aboutsummaryrefslogtreecommitdiff
path: root/contrib/bmake/unit-tests
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/bmake/unit-tests')
-rw-r--r--contrib/bmake/unit-tests/Makefile247
-rw-r--r--contrib/bmake/unit-tests/Makefile.config.in3
-rw-r--r--contrib/bmake/unit-tests/archive.exp20
-rw-r--r--contrib/bmake/unit-tests/archive.mk51
-rw-r--r--contrib/bmake/unit-tests/char-005c-reverse-solidus.exp13
-rw-r--r--contrib/bmake/unit-tests/char-005c-reverse-solidus.mk131
-rw-r--r--contrib/bmake/unit-tests/check-expect.lua189
-rw-r--r--contrib/bmake/unit-tests/cmd-errors-jobs.exp84
-rw-r--r--contrib/bmake/unit-tests/cmd-errors-jobs.mk118
-rw-r--r--contrib/bmake/unit-tests/cmd-errors-lint.exp15
-rw-r--r--contrib/bmake/unit-tests/cmd-errors-lint.mk29
-rw-r--r--contrib/bmake/unit-tests/cmd-errors.exp21
-rw-r--r--contrib/bmake/unit-tests/cmd-errors.mk37
-rwxr-xr-xcontrib/bmake/unit-tests/cmd-interrupt.exp2
-rwxr-xr-xcontrib/bmake/unit-tests/cmd-interrupt.mk26
-rw-r--r--contrib/bmake/unit-tests/cmdline-undefined.exp24
-rw-r--r--contrib/bmake/unit-tests/cmdline-undefined.mk16
-rw-r--r--contrib/bmake/unit-tests/cmdline.exp7
-rw-r--r--contrib/bmake/unit-tests/cmdline.mk32
-rw-r--r--contrib/bmake/unit-tests/comment.mk10
-rw-r--r--contrib/bmake/unit-tests/compat-error.exp2
-rw-r--r--contrib/bmake/unit-tests/compat-error.mk27
-rw-r--r--contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp4
-rwxr-xr-xcontrib/bmake/unit-tests/cond-cmp-numeric-eq.mk9
-rwxr-xr-xcontrib/bmake/unit-tests/cond-cmp-numeric-ge.mk4
-rwxr-xr-xcontrib/bmake/unit-tests/cond-cmp-numeric-gt.mk6
-rwxr-xr-xcontrib/bmake/unit-tests/cond-cmp-numeric-le.mk4
-rwxr-xr-xcontrib/bmake/unit-tests/cond-cmp-numeric-lt.mk4
-rwxr-xr-xcontrib/bmake/unit-tests/cond-cmp-numeric-ne.mk4
-rw-r--r--contrib/bmake/unit-tests/cond-cmp-numeric.exp10
-rw-r--r--contrib/bmake/unit-tests/cond-cmp-numeric.mk13
-rw-r--r--contrib/bmake/unit-tests/cond-cmp-string.exp16
-rw-r--r--contrib/bmake/unit-tests/cond-cmp-string.mk38
-rwxr-xr-xcontrib/bmake/unit-tests/cond-cmp-unary.exp2
-rwxr-xr-xcontrib/bmake/unit-tests/cond-cmp-unary.mk37
-rw-r--r--contrib/bmake/unit-tests/cond-eof.exp6
-rw-r--r--contrib/bmake/unit-tests/cond-eof.mk12
-rw-r--r--contrib/bmake/unit-tests/cond-func-commands.mk7
-rw-r--r--contrib/bmake/unit-tests/cond-func-defined.exp9
-rw-r--r--contrib/bmake/unit-tests/cond-func-defined.mk27
-rw-r--r--contrib/bmake/unit-tests/cond-func-empty.exp4
-rw-r--r--contrib/bmake/unit-tests/cond-func-empty.mk112
-rw-r--r--contrib/bmake/unit-tests/cond-func-exists.mk11
-rw-r--r--contrib/bmake/unit-tests/cond-func-make.exp1
-rw-r--r--contrib/bmake/unit-tests/cond-func-make.mk12
-rw-r--r--contrib/bmake/unit-tests/cond-func-target.mk7
-rw-r--r--contrib/bmake/unit-tests/cond-func.exp19
-rw-r--r--contrib/bmake/unit-tests/cond-func.mk40
-rw-r--r--contrib/bmake/unit-tests/cond-late.exp7
-rw-r--r--contrib/bmake/unit-tests/cond-late.mk29
-rw-r--r--contrib/bmake/unit-tests/cond-op-and-lint.exp2
-rw-r--r--contrib/bmake/unit-tests/cond-op-and-lint.mk3
-rw-r--r--contrib/bmake/unit-tests/cond-op-and.exp12
-rw-r--r--contrib/bmake/unit-tests/cond-op-and.mk35
-rw-r--r--contrib/bmake/unit-tests/cond-op-not.exp14
-rw-r--r--contrib/bmake/unit-tests/cond-op-not.mk8
-rw-r--r--contrib/bmake/unit-tests/cond-op-or-lint.exp2
-rw-r--r--contrib/bmake/unit-tests/cond-op-or-lint.mk3
-rw-r--r--contrib/bmake/unit-tests/cond-op-or.exp12
-rw-r--r--contrib/bmake/unit-tests/cond-op-or.mk53
-rw-r--r--contrib/bmake/unit-tests/cond-op-parentheses.exp10
-rw-r--r--contrib/bmake/unit-tests/cond-op-parentheses.mk11
-rw-r--r--contrib/bmake/unit-tests/cond-op.exp39
-rw-r--r--contrib/bmake/unit-tests/cond-op.mk28
-rw-r--r--contrib/bmake/unit-tests/cond-short.exp11
-rw-r--r--contrib/bmake/unit-tests/cond-short.mk161
-rw-r--r--contrib/bmake/unit-tests/cond-token-number.exp11
-rw-r--r--contrib/bmake/unit-tests/cond-token-number.mk30
-rw-r--r--contrib/bmake/unit-tests/cond-token-plain.exp47
-rw-r--r--contrib/bmake/unit-tests/cond-token-plain.mk75
-rw-r--r--contrib/bmake/unit-tests/cond-token-string.exp25
-rw-r--r--contrib/bmake/unit-tests/cond-token-string.mk38
-rw-r--r--contrib/bmake/unit-tests/cond-token-var.exp28
-rw-r--r--contrib/bmake/unit-tests/cond-token-var.mk106
-rwxr-xr-xcontrib/bmake/unit-tests/cond-undef-lint.exp7
-rwxr-xr-xcontrib/bmake/unit-tests/cond-undef-lint.mk16
-rw-r--r--contrib/bmake/unit-tests/cond1.exp23
-rw-r--r--contrib/bmake/unit-tests/cond1.mk114
-rw-r--r--contrib/bmake/unit-tests/dep-colon-bug-cross-file.exp4
-rw-r--r--contrib/bmake/unit-tests/dep-colon-bug-cross-file.mk4
-rw-r--r--contrib/bmake/unit-tests/dep-duplicate.exp4
-rw-r--r--contrib/bmake/unit-tests/dep-duplicate.mk8
-rw-r--r--contrib/bmake/unit-tests/dep-op-missing.exp3
-rw-r--r--contrib/bmake/unit-tests/dep-percent.exp2
-rwxr-xr-xcontrib/bmake/unit-tests/dep-var.exp28
-rwxr-xr-xcontrib/bmake/unit-tests/dep-var.mk26
-rw-r--r--contrib/bmake/unit-tests/dep-wildcards.mk8
-rw-r--r--contrib/bmake/unit-tests/dep.exp6
-rw-r--r--contrib/bmake/unit-tests/dep.mk14
-rw-r--r--contrib/bmake/unit-tests/depsrc-end.mk4
-rw-r--r--contrib/bmake/unit-tests/depsrc-ignore.exp4
-rw-r--r--contrib/bmake/unit-tests/depsrc-nopath.exp2
-rw-r--r--contrib/bmake/unit-tests/depsrc-nopath.mk25
-rw-r--r--contrib/bmake/unit-tests/depsrc-phony.mk3
-rw-r--r--contrib/bmake/unit-tests/depsrc-wait.exp13
-rw-r--r--contrib/bmake/unit-tests/depsrc-wait.mk22
-rw-r--r--contrib/bmake/unit-tests/deptgt-begin-fail-indirect.exp2
-rw-r--r--contrib/bmake/unit-tests/deptgt-begin-fail.exp2
-rw-r--r--contrib/bmake/unit-tests/deptgt-begin.exp4
-rw-r--r--contrib/bmake/unit-tests/deptgt-begin.mk12
-rw-r--r--contrib/bmake/unit-tests/deptgt-delete_on_error.exp18
-rw-r--r--contrib/bmake/unit-tests/deptgt-delete_on_error.mk2
-rw-r--r--contrib/bmake/unit-tests/deptgt-end-fail-all.exp2
-rw-r--r--contrib/bmake/unit-tests/deptgt-end-fail-indirect.exp2
-rw-r--r--contrib/bmake/unit-tests/deptgt-end-fail-indirect.mk4
-rw-r--r--contrib/bmake/unit-tests/deptgt-end-fail.exp30
-rw-r--r--contrib/bmake/unit-tests/deptgt-end-fail.mk4
-rw-r--r--contrib/bmake/unit-tests/deptgt-error.exp2
-rw-r--r--contrib/bmake/unit-tests/deptgt-ignore.exp2
-rw-r--r--contrib/bmake/unit-tests/deptgt-makeflags.exp4
-rw-r--r--contrib/bmake/unit-tests/deptgt-makeflags.mk6
-rw-r--r--contrib/bmake/unit-tests/deptgt-order.exp4
-rw-r--r--contrib/bmake/unit-tests/deptgt-path-suffix.exp4
-rw-r--r--contrib/bmake/unit-tests/deptgt-path-suffix.mk4
-rw-r--r--contrib/bmake/unit-tests/deptgt-posix.mk27
-rw-r--r--contrib/bmake/unit-tests/deptgt-suffixes.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt.exp33
-rw-r--r--contrib/bmake/unit-tests/deptgt.mk36
-rw-r--r--contrib/bmake/unit-tests/dir.mk8
-rwxr-xr-xcontrib/bmake/unit-tests/directive-dinclude.exp5
-rwxr-xr-xcontrib/bmake/unit-tests/directive-dinclude.mk4
-rw-r--r--contrib/bmake/unit-tests/directive-elif.exp38
-rw-r--r--contrib/bmake/unit-tests/directive-elif.mk25
-rw-r--r--contrib/bmake/unit-tests/directive-else.exp18
-rw-r--r--contrib/bmake/unit-tests/directive-else.mk14
-rw-r--r--contrib/bmake/unit-tests/directive-endfor.exp2
-rw-r--r--contrib/bmake/unit-tests/directive-endfor.mk3
-rw-r--r--contrib/bmake/unit-tests/directive-endif.exp10
-rw-r--r--contrib/bmake/unit-tests/directive-endif.mk28
-rw-r--r--contrib/bmake/unit-tests/directive-error.exp2
-rw-r--r--contrib/bmake/unit-tests/directive-error.mk3
-rw-r--r--contrib/bmake/unit-tests/directive-export-gmake.exp8
-rw-r--r--contrib/bmake/unit-tests/directive-export-gmake.mk48
-rw-r--r--contrib/bmake/unit-tests/directive-export-impl.exp36
-rw-r--r--contrib/bmake/unit-tests/directive-export-literal.exp3
-rw-r--r--contrib/bmake/unit-tests/directive-export-literal.mk25
-rw-r--r--contrib/bmake/unit-tests/directive-export.exp4
-rw-r--r--contrib/bmake/unit-tests/directive-export.mk33
-rw-r--r--contrib/bmake/unit-tests/directive-for-break.exp6
-rw-r--r--contrib/bmake/unit-tests/directive-for-break.mk66
-rw-r--r--contrib/bmake/unit-tests/directive-for-empty.exp22
-rw-r--r--contrib/bmake/unit-tests/directive-for-empty.mk126
-rw-r--r--contrib/bmake/unit-tests/directive-for-errors.exp31
-rw-r--r--contrib/bmake/unit-tests/directive-for-errors.mk68
-rw-r--r--contrib/bmake/unit-tests/directive-for-escape.exp220
-rw-r--r--contrib/bmake/unit-tests/directive-for-escape.mk183
-rwxr-xr-xcontrib/bmake/unit-tests/directive-for-generating-endif.exp13
-rwxr-xr-xcontrib/bmake/unit-tests/directive-for-generating-endif.mk6
-rw-r--r--contrib/bmake/unit-tests/directive-for-if.exp11
-rw-r--r--contrib/bmake/unit-tests/directive-for-if.mk7
-rw-r--r--contrib/bmake/unit-tests/directive-for-lines.exp12
-rw-r--r--contrib/bmake/unit-tests/directive-for-lines.mk18
-rw-r--r--contrib/bmake/unit-tests/directive-for-null.exp11
-rw-r--r--contrib/bmake/unit-tests/directive-for-null.mk20
-rwxr-xr-xcontrib/bmake/unit-tests/directive-for.exp66
-rwxr-xr-xcontrib/bmake/unit-tests/directive-for.mk191
-rwxr-xr-xcontrib/bmake/unit-tests/directive-hyphen-include.exp5
-rwxr-xr-xcontrib/bmake/unit-tests/directive-hyphen-include.mk4
-rw-r--r--contrib/bmake/unit-tests/directive-if-nested.exp2
-rw-r--r--contrib/bmake/unit-tests/directive-if-nested.mk4
-rw-r--r--contrib/bmake/unit-tests/directive-if.exp30
-rw-r--r--contrib/bmake/unit-tests/directive-if.mk12
-rw-r--r--contrib/bmake/unit-tests/directive-ifmake.exp16
-rw-r--r--contrib/bmake/unit-tests/directive-ifmake.mk11
-rw-r--r--contrib/bmake/unit-tests/directive-ifndef.exp2
-rw-r--r--contrib/bmake/unit-tests/directive-ifndef.mk67
-rwxr-xr-xcontrib/bmake/unit-tests/directive-include-fatal.exp4
-rwxr-xr-xcontrib/bmake/unit-tests/directive-include-fatal.mk3
-rw-r--r--contrib/bmake/unit-tests/directive-include-guard.exp106
-rw-r--r--contrib/bmake/unit-tests/directive-include-guard.mk648
-rwxr-xr-xcontrib/bmake/unit-tests/directive-include.exp15
-rwxr-xr-xcontrib/bmake/unit-tests/directive-include.mk10
-rw-r--r--contrib/bmake/unit-tests/directive-info.exp26
-rw-r--r--contrib/bmake/unit-tests/directive-info.mk21
-rw-r--r--contrib/bmake/unit-tests/directive-misspellings.exp84
-rw-r--r--contrib/bmake/unit-tests/directive-misspellings.mk46
-rwxr-xr-xcontrib/bmake/unit-tests/directive-sinclude.exp5
-rwxr-xr-xcontrib/bmake/unit-tests/directive-sinclude.mk4
-rw-r--r--contrib/bmake/unit-tests/directive-undef.exp9
-rw-r--r--contrib/bmake/unit-tests/directive-undef.mk5
-rw-r--r--contrib/bmake/unit-tests/directive-unexport-env.exp8
-rw-r--r--contrib/bmake/unit-tests/directive-unexport-env.mk5
-rw-r--r--contrib/bmake/unit-tests/directive-unexport.exp8
-rw-r--r--contrib/bmake/unit-tests/directive-unexport.mk6
-rw-r--r--contrib/bmake/unit-tests/directive-warning.exp18
-rw-r--r--contrib/bmake/unit-tests/directive-warning.mk12
-rw-r--r--contrib/bmake/unit-tests/directive.exp16
-rw-r--r--contrib/bmake/unit-tests/directive.mk12
-rw-r--r--contrib/bmake/unit-tests/doterror.exp2
-rw-r--r--contrib/bmake/unit-tests/doterror.mk3
-rw-r--r--contrib/bmake/unit-tests/error.exp6
-rw-r--r--contrib/bmake/unit-tests/error.mk5
-rw-r--r--contrib/bmake/unit-tests/escape.exp36
-rw-r--r--contrib/bmake/unit-tests/escape.mk7
-rw-r--r--contrib/bmake/unit-tests/export-all.mk4
-rw-r--r--contrib/bmake/unit-tests/export-env.mk6
-rw-r--r--contrib/bmake/unit-tests/export.mk4
-rw-r--r--contrib/bmake/unit-tests/forloop.exp20
-rw-r--r--contrib/bmake/unit-tests/forloop.mk53
-rw-r--r--contrib/bmake/unit-tests/forsubst.exp2
-rw-r--r--contrib/bmake/unit-tests/forsubst.mk22
-rw-r--r--contrib/bmake/unit-tests/gnode-submake.exp2
-rw-r--r--contrib/bmake/unit-tests/hanoi-include.mk42
-rw-r--r--contrib/bmake/unit-tests/include-main.exp30
-rw-r--r--contrib/bmake/unit-tests/include-main.mk8
-rw-r--r--contrib/bmake/unit-tests/include-sub.inc (renamed from contrib/bmake/unit-tests/include-sub.mk)4
-rw-r--r--contrib/bmake/unit-tests/include-subsub.inc9
-rw-r--r--contrib/bmake/unit-tests/include-subsub.mk9
-rw-r--r--contrib/bmake/unit-tests/job-output-null.exp6
-rw-r--r--contrib/bmake/unit-tests/job-output-null.mk36
-rw-r--r--contrib/bmake/unit-tests/job-output.exp13
-rw-r--r--contrib/bmake/unit-tests/job-output.mk41
-rw-r--r--contrib/bmake/unit-tests/jobs-empty-commands-error.exp2
-rw-r--r--contrib/bmake/unit-tests/jobs-error-indirect.exp6
-rw-r--r--contrib/bmake/unit-tests/jobs-error-nested-make.exp8
-rw-r--r--contrib/bmake/unit-tests/jobs-error-nested.exp12
-rwxr-xr-xcontrib/bmake/unit-tests/lint.exp7
-rwxr-xr-xcontrib/bmake/unit-tests/lint.mk4
-rwxr-xr-xcontrib/bmake/unit-tests/make-exported.mk4
-rw-r--r--contrib/bmake/unit-tests/meta-cmd-cmp.exp12
-rw-r--r--contrib/bmake/unit-tests/meta-ignore.inc63
-rw-r--r--contrib/bmake/unit-tests/moderrs.exp290
-rw-r--r--contrib/bmake/unit-tests/moderrs.mk142
-rw-r--r--contrib/bmake/unit-tests/modmatch.exp17
-rw-r--r--contrib/bmake/unit-tests/modmatch.mk30
-rw-r--r--contrib/bmake/unit-tests/modmisc.exp1
-rw-r--r--contrib/bmake/unit-tests/modmisc.mk10
-rw-r--r--contrib/bmake/unit-tests/objdir-writable.exp2
-rw-r--r--contrib/bmake/unit-tests/opt-chdir.exp2
-rw-r--r--contrib/bmake/unit-tests/opt-chdir.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-debug-errors-jobs.exp18
-rw-r--r--contrib/bmake/unit-tests/opt-debug-errors.exp2
-rw-r--r--contrib/bmake/unit-tests/opt-debug-file.exp14
-rw-r--r--contrib/bmake/unit-tests/opt-debug-file.mk32
-rw-r--r--contrib/bmake/unit-tests/opt-debug-for.exp12
-rw-r--r--contrib/bmake/unit-tests/opt-debug-graph1.exp6
-rw-r--r--contrib/bmake/unit-tests/opt-debug-graph2.exp8
-rw-r--r--contrib/bmake/unit-tests/opt-debug-graph3.exp8
-rw-r--r--contrib/bmake/unit-tests/opt-debug-hash.exp6
-rw-r--r--contrib/bmake/unit-tests/opt-debug-hash.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-debug-jobs.exp20
-rw-r--r--contrib/bmake/unit-tests/opt-debug-jobs.mk4
-rw-r--r--contrib/bmake/unit-tests/opt-debug-lint.exp13
-rw-r--r--contrib/bmake/unit-tests/opt-debug-lint.mk15
-rw-r--r--contrib/bmake/unit-tests/opt-debug-loud.mk6
-rw-r--r--contrib/bmake/unit-tests/opt-debug-parse.exp34
-rw-r--r--contrib/bmake/unit-tests/opt-debug-parse.mk5
-rw-r--r--contrib/bmake/unit-tests/opt-debug-var.exp8
-rw-r--r--contrib/bmake/unit-tests/opt-debug-var.mk21
-rw-r--r--contrib/bmake/unit-tests/opt-define.mk16
-rw-r--r--contrib/bmake/unit-tests/opt-env.exp6
-rw-r--r--contrib/bmake/unit-tests/opt-file.exp9
-rw-r--r--contrib/bmake/unit-tests/opt-file.mk12
-rw-r--r--contrib/bmake/unit-tests/opt-jobs-internal.exp21
-rw-r--r--contrib/bmake/unit-tests/opt-jobs-internal.mk70
-rw-r--r--contrib/bmake/unit-tests/opt-jobs-no-action.mk4
-rw-r--r--contrib/bmake/unit-tests/opt-jobs.mk56
-rw-r--r--contrib/bmake/unit-tests/opt-keep-going-indirect.exp8
-rw-r--r--contrib/bmake/unit-tests/opt-keep-going-indirect.mk10
-rw-r--r--contrib/bmake/unit-tests/opt-keep-going-multiple.exp2
-rw-r--r--contrib/bmake/unit-tests/opt-keep-going.exp2
-rw-r--r--contrib/bmake/unit-tests/opt-m-include-dir.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-query.exp24
-rw-r--r--contrib/bmake/unit-tests/opt-query.mk69
-rw-r--r--contrib/bmake/unit-tests/opt-touch-jobs.mk4
-rw-r--r--contrib/bmake/unit-tests/opt-tracefile.exp16
-rw-r--r--contrib/bmake/unit-tests/opt-tracefile.mk5
-rw-r--r--contrib/bmake/unit-tests/opt-version.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-warnings-as-errors.exp6
-rw-r--r--contrib/bmake/unit-tests/opt-warnings-as-errors.mk4
-rw-r--r--contrib/bmake/unit-tests/opt-x-reduce-exported.exp4
-rw-r--r--contrib/bmake/unit-tests/opt-x-reduce-exported.mk22
-rw-r--r--contrib/bmake/unit-tests/opt.exp2
-rw-r--r--contrib/bmake/unit-tests/opt.mk4
-rw-r--r--contrib/bmake/unit-tests/parse-var.mk126
-rw-r--r--contrib/bmake/unit-tests/parse.exp7
-rw-r--r--contrib/bmake/unit-tests/parse.mk47
-rw-r--r--contrib/bmake/unit-tests/posix-execution.exp24
-rw-r--r--contrib/bmake/unit-tests/posix-execution.mk59
-rw-r--r--contrib/bmake/unit-tests/posix-expansion.exp1
-rw-r--r--contrib/bmake/unit-tests/posix-expansion.mk22
-rw-r--r--contrib/bmake/unit-tests/posix-varassign.exp1
-rw-r--r--contrib/bmake/unit-tests/posix-varassign.mk79
-rw-r--r--contrib/bmake/unit-tests/posix.exp26
-rw-r--r--contrib/bmake/unit-tests/posix.mk36
-rw-r--r--contrib/bmake/unit-tests/recursive.exp4
-rw-r--r--contrib/bmake/unit-tests/recursive.mk19
-rwxr-xr-xcontrib/bmake/unit-tests/sh-dots.mk6
-rw-r--r--contrib/bmake/unit-tests/sh-errctl.exp14
-rw-r--r--contrib/bmake/unit-tests/sh-jobs.exp2
-rw-r--r--contrib/bmake/unit-tests/sh-leading-at.exp1
-rw-r--r--contrib/bmake/unit-tests/sh-leading-at.mk6
-rw-r--r--contrib/bmake/unit-tests/sh-leading-hyphen.exp10
-rw-r--r--contrib/bmake/unit-tests/sh-leading-hyphen.mk19
-rw-r--r--contrib/bmake/unit-tests/sh-leading-plus.exp2
-rw-r--r--contrib/bmake/unit-tests/sh-leading-plus.mk6
-rw-r--r--contrib/bmake/unit-tests/shell-csh.mk8
-rw-r--r--contrib/bmake/unit-tests/shell-ksh.exp11
-rw-r--r--contrib/bmake/unit-tests/shell-ksh.mk42
-rw-r--r--contrib/bmake/unit-tests/shell-sh.mk4
-rw-r--r--contrib/bmake/unit-tests/suff-add-later.exp2
-rw-r--r--contrib/bmake/unit-tests/suff-clear-regular.exp2
-rw-r--r--contrib/bmake/unit-tests/suff-clear-single.exp2
-rw-r--r--contrib/bmake/unit-tests/suff-incomplete.exp14
-rw-r--r--contrib/bmake/unit-tests/suff-main-several.exp40
-rw-r--r--contrib/bmake/unit-tests/suff-rebuild.exp22
-rw-r--r--contrib/bmake/unit-tests/suff-self.exp2
-rw-r--r--contrib/bmake/unit-tests/suff-transform-debug.exp6
-rw-r--r--contrib/bmake/unit-tests/suff-transform-endless.exp2
-rw-r--r--contrib/bmake/unit-tests/suff-transform-expand.exp2
-rw-r--r--contrib/bmake/unit-tests/suff-transform-select.exp2
-rw-r--r--contrib/bmake/unit-tests/suff-use.exp2
-rw-r--r--contrib/bmake/unit-tests/suff.exp146
-rw-r--r--contrib/bmake/unit-tests/suff.mk41
-rw-r--r--contrib/bmake/unit-tests/unexport.mk4
-rw-r--r--contrib/bmake/unit-tests/use-inference.exp2
-rw-r--r--contrib/bmake/unit-tests/var-eval-short.exp30
-rw-r--r--contrib/bmake/unit-tests/var-eval-short.mk15
-rw-r--r--contrib/bmake/unit-tests/var-op-append.mk50
-rw-r--r--contrib/bmake/unit-tests/var-op-assign.exp6
-rw-r--r--contrib/bmake/unit-tests/var-op-assign.mk6
-rw-r--r--contrib/bmake/unit-tests/var-op-default.mk8
-rw-r--r--contrib/bmake/unit-tests/var-op-expand.exp22
-rw-r--r--contrib/bmake/unit-tests/var-op-expand.mk34
-rw-r--r--contrib/bmake/unit-tests/var-op-shell.exp14
-rw-r--r--contrib/bmake/unit-tests/var-op-shell.mk39
-rw-r--r--contrib/bmake/unit-tests/var-readonly.exp4
-rw-r--r--contrib/bmake/unit-tests/var-readonly.mk27
-rw-r--r--contrib/bmake/unit-tests/var-recursive.exp46
-rw-r--r--contrib/bmake/unit-tests/var-recursive.mk75
-rw-r--r--contrib/bmake/unit-tests/var-scope-cmdline.exp4
-rw-r--r--contrib/bmake/unit-tests/var-scope-cmdline.mk10
-rw-r--r--contrib/bmake/unit-tests/var-scope-local-legacy.exp5
-rw-r--r--contrib/bmake/unit-tests/var-scope-local-legacy.mk35
-rw-r--r--contrib/bmake/unit-tests/var-scope-local.exp72
-rw-r--r--contrib/bmake/unit-tests/var-scope-local.mk128
-rw-r--r--contrib/bmake/unit-tests/varcmd.mk6
-rw-r--r--contrib/bmake/unit-tests/vardebug.exp64
-rw-r--r--contrib/bmake/unit-tests/vardebug.mk42
-rw-r--r--contrib/bmake/unit-tests/varmisc.exp43
-rw-r--r--contrib/bmake/unit-tests/varmisc.mk45
-rw-r--r--contrib/bmake/unit-tests/varmod-assign-shell.exp12
-rw-r--r--contrib/bmake/unit-tests/varmod-assign-shell.mk17
-rw-r--r--contrib/bmake/unit-tests/varmod-assign.exp77
-rw-r--r--contrib/bmake/unit-tests/varmod-assign.mk103
-rw-r--r--contrib/bmake/unit-tests/varmod-defined.exp2
-rw-r--r--contrib/bmake/unit-tests/varmod-defined.mk20
-rw-r--r--contrib/bmake/unit-tests/varmod-edge.exp41
-rw-r--r--contrib/bmake/unit-tests/varmod-edge.mk217
-rw-r--r--contrib/bmake/unit-tests/varmod-gmtime.exp22
-rw-r--r--contrib/bmake/unit-tests/varmod-gmtime.mk78
-rw-r--r--contrib/bmake/unit-tests/varmod-hash.exp20
-rw-r--r--contrib/bmake/unit-tests/varmod-hash.mk9
-rw-r--r--contrib/bmake/unit-tests/varmod-head.exp10
-rw-r--r--contrib/bmake/unit-tests/varmod-head.mk71
-rw-r--r--contrib/bmake/unit-tests/varmod-ifelse.exp84
-rw-r--r--contrib/bmake/unit-tests/varmod-ifelse.mk196
-rw-r--r--contrib/bmake/unit-tests/varmod-indirect.exp40
-rw-r--r--contrib/bmake/unit-tests/varmod-indirect.mk72
-rw-r--r--contrib/bmake/unit-tests/varmod-l-name-to-value.mk4
-rw-r--r--contrib/bmake/unit-tests/varmod-localtime.exp22
-rw-r--r--contrib/bmake/unit-tests/varmod-localtime.mk38
-rw-r--r--contrib/bmake/unit-tests/varmod-loop-delete.exp6
-rw-r--r--contrib/bmake/unit-tests/varmod-loop-delete.mk3
-rw-r--r--contrib/bmake/unit-tests/varmod-loop-varname.exp18
-rw-r--r--contrib/bmake/unit-tests/varmod-loop-varname.mk8
-rw-r--r--contrib/bmake/unit-tests/varmod-loop.exp14
-rw-r--r--contrib/bmake/unit-tests/varmod-loop.mk81
-rwxr-xr-xcontrib/bmake/unit-tests/varmod-match-escape.exp37
-rwxr-xr-xcontrib/bmake/unit-tests/varmod-match-escape.mk57
-rw-r--r--contrib/bmake/unit-tests/varmod-match.exp33
-rw-r--r--contrib/bmake/unit-tests/varmod-match.mk376
-rw-r--r--contrib/bmake/unit-tests/varmod-mtime.exp15
-rw-r--r--contrib/bmake/unit-tests/varmod-mtime.mk120
-rw-r--r--contrib/bmake/unit-tests/varmod-no-match.mk98
-rw-r--r--contrib/bmake/unit-tests/varmod-order-numeric.mk8
-rw-r--r--contrib/bmake/unit-tests/varmod-order-shuffle.mk7
-rw-r--r--contrib/bmake/unit-tests/varmod-order.exp47
-rw-r--r--contrib/bmake/unit-tests/varmod-order.mk34
-rw-r--r--contrib/bmake/unit-tests/varmod-path.mk14
-rw-r--r--contrib/bmake/unit-tests/varmod-quote-dollar.exp2
-rw-r--r--contrib/bmake/unit-tests/varmod-quote-dollar.mk11
-rw-r--r--contrib/bmake/unit-tests/varmod-range.exp24
-rw-r--r--contrib/bmake/unit-tests/varmod-range.mk31
-rw-r--r--contrib/bmake/unit-tests/varmod-remember.mk69
-rw-r--r--contrib/bmake/unit-tests/varmod-select-words.exp74
-rw-r--r--contrib/bmake/unit-tests/varmod-select-words.mk66
-rw-r--r--contrib/bmake/unit-tests/varmod-shell.exp14
-rw-r--r--contrib/bmake/unit-tests/varmod-shell.mk14
-rw-r--r--contrib/bmake/unit-tests/varmod-subst-regex.exp60
-rw-r--r--contrib/bmake/unit-tests/varmod-subst-regex.mk71
-rw-r--r--contrib/bmake/unit-tests/varmod-subst.exp8
-rw-r--r--contrib/bmake/unit-tests/varmod-subst.mk127
-rw-r--r--contrib/bmake/unit-tests/varmod-sun-shell.exp16
-rw-r--r--contrib/bmake/unit-tests/varmod-sun-shell.mk14
-rw-r--r--contrib/bmake/unit-tests/varmod-sysv.exp8
-rw-r--r--contrib/bmake/unit-tests/varmod-sysv.mk46
-rw-r--r--contrib/bmake/unit-tests/varmod-tail.mk10
-rw-r--r--contrib/bmake/unit-tests/varmod-to-abs.exp6
-rw-r--r--contrib/bmake/unit-tests/varmod-to-abs.mk5
-rw-r--r--contrib/bmake/unit-tests/varmod-to-lower.mk15
-rw-r--r--contrib/bmake/unit-tests/varmod-to-separator.exp48
-rw-r--r--contrib/bmake/unit-tests/varmod-to-separator.mk93
-rw-r--r--contrib/bmake/unit-tests/varmod-to-title.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-to-title.mk31
-rw-r--r--contrib/bmake/unit-tests/varmod-undefined.mk57
-rw-r--r--contrib/bmake/unit-tests/varmod.exp56
-rw-r--r--contrib/bmake/unit-tests/varmod.mk227
-rw-r--r--contrib/bmake/unit-tests/varname-circumflex.exp9
-rw-r--r--contrib/bmake/unit-tests/varname-circumflex.mk47
-rw-r--r--contrib/bmake/unit-tests/varname-dollar.exp8
-rw-r--r--contrib/bmake/unit-tests/varname-dollar.mk6
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-jobs.exp2
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-jobs.mk27
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-level.exp11
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-level.mk57
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.exp10
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.mk7
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.exp10
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.mk7
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.exp10
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.mk7
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-mode.exp30
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-mode.mk41
-rw-r--r--contrib/bmake/unit-tests/varname-dot-makeflags.exp11
-rw-r--r--contrib/bmake/unit-tests/varname-dot-makeflags.mk42
-rw-r--r--contrib/bmake/unit-tests/varname-dot-makeoverrides.exp7
-rw-r--r--contrib/bmake/unit-tests/varname-dot-makeoverrides.mk25
-rw-r--r--contrib/bmake/unit-tests/varname-dot-newline.exp11
-rw-r--r--contrib/bmake/unit-tests/varname-dot-newline.mk43
-rw-r--r--contrib/bmake/unit-tests/varname-dot-objdir.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-dot-objdir.mk11
-rw-r--r--contrib/bmake/unit-tests/varname-dot-parsedir.exp6
-rw-r--r--contrib/bmake/unit-tests/varname-dot-parsedir.mk12
-rw-r--r--contrib/bmake/unit-tests/varname-dot-parsefile.exp6
-rw-r--r--contrib/bmake/unit-tests/varname-dot-parsefile.mk12
-rwxr-xr-xcontrib/bmake/unit-tests/varname-dot-shell.exp37
-rw-r--r--contrib/bmake/unit-tests/varname-dot-suffixes.exp34
-rw-r--r--contrib/bmake/unit-tests/varname-dot-suffixes.mk19
-rw-r--r--contrib/bmake/unit-tests/varname-empty.exp24
-rwxr-xr-xcontrib/bmake/unit-tests/varname-empty.mk4
-rw-r--r--contrib/bmake/unit-tests/varname-make_print_var_on_error-jobs.exp2
-rw-r--r--contrib/bmake/unit-tests/varname-make_print_var_on_error-jobs.mk4
-rw-r--r--contrib/bmake/unit-tests/varname-make_print_var_on_error.exp2
-rw-r--r--contrib/bmake/unit-tests/varname-make_stack_trace.exp40
-rw-r--r--contrib/bmake/unit-tests/varname-make_stack_trace.mk37
-rw-r--r--contrib/bmake/unit-tests/varname-makeflags.exp20
-rw-r--r--contrib/bmake/unit-tests/varname-makeflags.mk182
-rw-r--r--contrib/bmake/unit-tests/varname-vpath.exp8
-rw-r--r--contrib/bmake/unit-tests/varname.exp21
-rw-r--r--contrib/bmake/unit-tests/varname.mk73
-rw-r--r--contrib/bmake/unit-tests/varparse-dynamic.exp4
-rw-r--r--contrib/bmake/unit-tests/varparse-dynamic.mk11
-rw-r--r--contrib/bmake/unit-tests/varparse-errors.exp57
-rw-r--r--contrib/bmake/unit-tests/varparse-errors.mk70
-rw-r--r--contrib/bmake/unit-tests/varparse-mod.mk6
-rw-r--r--contrib/bmake/unit-tests/varparse-undef-partial.mk13
-rw-r--r--contrib/bmake/unit-tests/varquote.exp3
-rw-r--r--contrib/bmake/unit-tests/varquote.mk14
459 files changed, 9811 insertions, 3495 deletions
diff --git a/contrib/bmake/unit-tests/Makefile b/contrib/bmake/unit-tests/Makefile
index c3b64abad84a..4e639056815a 100644
--- a/contrib/bmake/unit-tests/Makefile
+++ b/contrib/bmake/unit-tests/Makefile
@@ -1,6 +1,6 @@
-# $Id: Makefile,v 1.180 2022/04/18 21:25:37 sjg Exp $
+# $Id: Makefile,v 1.240 2025/06/30 18:40:54 sjg Exp $
#
-# $NetBSD: Makefile,v 1.312 2022/04/18 15:06:28 rillig Exp $
+# $NetBSD: Makefile,v 1.369 2025/06/29 09:40:13 rillig Exp $
#
# Unit tests for make(1)
#
@@ -25,10 +25,6 @@
# named makefile (*.mk), with its own set of expected results (*.exp),
# and it should be added to the TESTS list.
#
-# A few *.mk files are helper files for other tests (such as include-sub.mk)
-# and are thus not added to TESTS. Such files must be ignored in
-# src/tests/usr.bin/make/t_make.sh.
-#
.MAIN: all
@@ -36,12 +32,33 @@
.MAKE.OS?= ${uname -s:L:sh}
.MAKE.UID?= ${id -u:L:sh}
+# for many tests we need a TMPDIR that will not collide
+# with other users.
+.if ${.OBJDIR} != ${.CURDIR}
+# easy
+TMPDIR:= ${.OBJDIR}/tmp
+.elif defined(TMPDIR)
+TMPDIR:= ${TMPDIR}/uid${.MAKE.UID}
+.else
+TMPDIR:= /tmp/uid${.MAKE.UID}
+.endif
+# make sure it exists
+.if !exist(${TMPDIR})
+_!= mkdir -p ${TMPDIR}
+.endif
+# and clean it up - outside the context of
+# any target that might be using it.
+.END: rm-tmpdir
+rm-tmpdir: .NOMETA
+ @rm -rf ${TMPDIR}
+
# Each test is in a sub-makefile.
# Keep the list sorted.
# Any test that is commented out must be ignored in
# src/tests/usr.bin/make/t_make.sh as well.
#TESTS+= archive
#TESTS+= archive-suffix
+TESTS+= char-005c-reverse-solidus
TESTS+= cmd-errors
TESTS+= cmd-errors-jobs
TESTS+= cmd-errors-lint
@@ -83,7 +100,6 @@ TESTS+= cond-token-plain
TESTS+= cond-token-string
TESTS+= cond-token-var
TESTS+= cond-undef-lint
-TESTS+= cond1
TESTS+= counter
TESTS+= counter-append
TESTS+= dep
@@ -168,6 +184,8 @@ TESTS+= directive-export-impl
TESTS+= directive-export-gmake
TESTS+= directive-export-literal
TESTS+= directive-for
+TESTS+= directive-for-break
+TESTS+= directive-for-empty
TESTS+= directive-for-errors
TESTS+= directive-for-escape
TESTS+= directive-for-generating-endif
@@ -183,6 +201,7 @@ TESTS+= directive-ifndef
TESTS+= directive-ifnmake
TESTS+= directive-include
TESTS+= directive-include-fatal
+TESTS+= directive-include-guard
TESTS+= directive-info
TESTS+= directive-misspellings
TESTS+= directive-sinclude
@@ -199,14 +218,13 @@ TESTS+= export
TESTS+= export-all
TESTS+= export-env
TESTS+= export-variants
-TESTS+= forloop
-TESTS+= forsubst
TESTS+= gnode-submake
TESTS+= hanoi-include
TESTS+= impsrc
TESTS+= include-main
TESTS+= job-flags
-#TESTS+= job-output-long-lines
+TESTS+= job-output
+TESTS+= job-output-long-lines
TESTS+= job-output-null
TESTS+= jobs-empty-commands
TESTS+= jobs-empty-commands-error
@@ -217,7 +235,6 @@ TESTS+= lint
TESTS+= make-exported
TESTS+= meta-cmd-cmp
TESTS+= moderrs
-TESTS+= modmatch
TESTS+= modmisc
.if ${.MAKE.UID} > 0
TESTS+= objdir-writable
@@ -239,7 +256,7 @@ TESTS+= opt-debug-graph1
TESTS+= opt-debug-graph2
TESTS+= opt-debug-graph3
TESTS+= opt-debug-hash
-#TESTS+= opt-debug-jobs
+TESTS+= opt-debug-jobs
TESTS+= opt-debug-lint
TESTS+= opt-debug-loud
TESTS+= opt-debug-meta
@@ -284,6 +301,9 @@ TESTS+= parse
TESTS+= parse-var
TESTS+= phony-end
TESTS+= posix
+TESTS+= posix-execution
+TESTS+= posix-expansion
+TESTS+= posix-varassign
TESTS+= # posix1 # broken by reverting POSIX changes
TESTS+= recursive
TESTS+= sh
@@ -304,6 +324,7 @@ TESTS+= shell-custom
TESTS+= shell-ksh
.endif
TESTS+= shell-sh
+TESTS+= suff
TESTS+= suff-add-later
TESTS+= suff-clear-regular
TESTS+= suff-clear-single
@@ -324,6 +345,7 @@ TESTS+= ternary
TESTS+= unexport
TESTS+= unexport-env
TESTS+= use-inference
+TESTS+= var-readonly
TESTS+= var-scope
TESTS+= var-scope-cmdline
TESTS+= var-scope-env
@@ -362,6 +384,7 @@ TESTS+= varmod-loop-delete
TESTS+= varmod-loop-varname
TESTS+= varmod-match
TESTS+= varmod-match-escape
+TESTS+= varmod-mtime
TESTS+= varmod-no-match
TESTS+= varmod-order
TESTS+= varmod-order-numeric
@@ -386,10 +409,12 @@ TESTS+= varmod-to-lower
TESTS+= varmod-to-many-words
TESTS+= varmod-to-one-word
TESTS+= varmod-to-separator
+TESTS+= varmod-to-title
TESTS+= varmod-to-upper
TESTS+= varmod-undefined
TESTS+= varmod-unique
TESTS+= varname
+TESTS+= varname-circumflex
TESTS+= varname-dollar
TESTS+= varname-dot-alltargets
TESTS+= varname-dot-curdir
@@ -408,12 +433,20 @@ TESTS+= varname-dot-make-makefiles
TESTS+= varname-dot-make-meta-bailiwick
TESTS+= varname-dot-make-meta-created
TESTS+= varname-dot-make-meta-files
+.if ${.MAKE.PATH_FILEMON:Uno:Nktrace:N/dev*} == "" && ${TMPDIR:N/tmp*:N/var/tmp*} != ""
+# these tests will not work if TMPDIR is or is a subdir of
+# /tmp or /var/tmp
+.if ${.MAKE.PATH_FILEMON:N/dev/*} != "" || exists(${.MAKE.PATH_FILEMON})
TESTS+= varname-dot-make-meta-ignore_filter
TESTS+= varname-dot-make-meta-ignore_paths
TESTS+= varname-dot-make-meta-ignore_patterns
+TESTS+= varname-dot-make-path_filemon
+.else
+.warning Skipping tests that require ${.MAKE.PATH_FILEMON}
+.endif
+.endif
TESTS+= varname-dot-make-meta-prefix
TESTS+= varname-dot-make-mode
-TESTS+= varname-dot-make-path_filemon
TESTS+= varname-dot-make-pid
TESTS+= varname-dot-make-ppid
TESTS+= varname-dot-make-save_dollars
@@ -429,6 +462,7 @@ TESTS+= varname-dot-suffixes
TESTS+= varname-dot-targets
TESTS+= varname-empty
TESTS+= varname-make
+TESTS+= varname-make_stack_trace
TESTS+= varname-make_print_var_on_error
TESTS+= varname-make_print_var_on_error-jobs
TESTS+= varname-makefile
@@ -439,21 +473,51 @@ TESTS+= varparse-dynamic
TESTS+= varparse-errors
TESTS+= varparse-mod
TESTS+= varparse-undef-partial
-TESTS+= varquote
# some shells have quirks
_shell := ${.SHELL:tA:T}
.if ${_shell} == "dash"
# dash fails -x output
BROKEN_TESTS+= opt-debug-x-trace
-.elif ${_shell} == "ksh"
-BROKEN_TESTS+= sh-flags
+.elif ${_shell:N*ksh*} == ""
+BROKEN_TESTS+= \
+ deptgt-silent-jobs \
+ job-flags \
+ job-output-long-lines \
+ opt-debug-x-trace \
+ sh-flags \
+ var-op-shell \
+
+.if ${_shell:Nmksh} == ""
+# more broken that pdksh
+BROKEN_TESTS+= \
+ opt-jobs-no-action \
+ sh-errctl \
+ sh-leading-hyphen \
+
+.endif
+.endif
+
+.if ${UTC_1:Uno} == ""
+# this will not work if UTC_1 is set empty
+BROKEN_TESTS+= varmod-localtime
.endif
.if ${.MAKE.OS:NDarwin} == ""
BROKEN_TESTS+= shell-ksh
.endif
+.if ${.MAKE.OS:NIRIX*} == ""
+BROKEN_TESTS+= \
+ cmd-interrupt \
+ deptgt-interrupt \
+ job-output-null \
+ opt-chdir \
+ opt-debug-x-trace \
+ sh-leading-hyphen \
+
+.endif
+
.if ${.MAKE.OS} == "SCO_SV"
BROKEN_TESTS+= \
opt-debug-graph[23] \
@@ -479,7 +543,6 @@ TESTS:= ${TESTS:${BROKEN_TESTS:S,^,N,:ts:}}
# Ideas for more tests:
# char-0020-space.mk
-# char-005C-backslash.mk
# escape-cond-str.mk
# escape-cond-func-arg.mk
# escape-varmod.mk
@@ -501,7 +564,8 @@ TESTS:= ${TESTS:${BROKEN_TESTS:S,^,N,:ts:}}
ENV.depsrc-optional+= TZ=UTC
ENV.deptgt-phony+= MAKESYSPATH=.
ENV.directive-undef= ENV_VAR=env-value
-ENV.envfirst= FROM_ENV=value-from-env
+ENV.opt-env= FROM_ENV=value-from-env
+ENV.opt-m-include-dir= ${MAKEOBJDIR:DMAKEOBJDIR=${MAKEOBJDIR}}
ENV.varmisc= FROM_ENV=env
ENV.varmisc+= FROM_ENV_BEFORE=env
ENV.varmisc+= FROM_ENV_AFTER=env
@@ -521,7 +585,16 @@ FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmdline-plain'
# Some tests need extra postprocessing.
SED_CMDS.deptgt-phony= ${STD_SED_CMDS.dd}
SED_CMDS.dir= ${STD_SED_CMDS.dd}
+SED_CMDS.directive-include-guard= \
+ -e '/\.MAKEFLAGS/d' \
+ -e '/^Parsing .*:[1-9][0-9]*:/d' \
+ -e '/^SetFilenameVars:/d' \
+ -e '/^ParseDependency/d' \
+ -e '/^ParseEOF:/d'
SED_CMDS.export= -e '/^[^=_A-Za-z0-9]*=/d'
+.if ${.MAKE.OS:NCygwin} == ""
+SED_CMDS.export+= -e '/^WINDIR=/d' -e '/^SYSTEMROOT=/d'
+.endif
SED_CMDS.export-all= ${SED_CMDS.export}
SED_CMDS.export-env= ${SED_CMDS.export}
SED_CMDS.cmdline= -e 's,uid${.MAKE.UID}/,,'
@@ -540,17 +613,16 @@ SED_CMDS.opt-chdir= -e 's,\(nonexistent\).[1-9][0-9]*,\1,' \
-e '/name/s,file,File,' \
-e 's,no such,No such,' \
-e 's,Filename,File name,'
+
+# meta line numbers can vary based on filemon implementation
+SED_CMDS.meta-ignore= -e 's,\(\.meta:\)[1-9][0-9]*:,\1<line>:,'
+
+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>,'
-SED_CMDS.opt-debug-jobs+= -e 's,JobFinish: [0-9][0-9]*,JobFinish: <pid>,'
-SED_CMDS.opt-debug-jobs+= -e 's,Command: ${.SHELL:T},Command: <shell>,'
-# The "-q" may be there or not, see jobs.c, variable shells.
-SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: <shell>\) -q,\1,'
+SED_CMDS.opt-debug-hash= -e 's,\(entries\)=[1-9][0-9],\1=<entries>,'
+SED_CMDS.opt-debug-jobs= ${STD_SED_CMDS.dj}
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}
@@ -558,11 +630,15 @@ 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.
-SED_CMDS.sh-dots+= -e 's,^make: exec(\(.*\)) failed (.*)$$,<not found: \1>,'
+SED_CMDS.sh-dots+= -e 's,^make: exec(\(.*\)): .*$$,<not found: \1>,'
SED_CMDS.sh-dots+= -e 's,^\(\*\*\* Error code \)[1-9][0-9]*,\1<nonzero>,'
+# Race condition between the child's stdout and make's status.
SED_CMDS.sh-errctl= ${STD_SED_CMDS.dj}
+SED_CMDS.sh-errctl+= -e '/^Process with pid/d'
+SED_CMDS.sh-errctl+= -e '/^JobFinish:/d'
SED_CMDS.sh-flags= ${STD_SED_CMDS.hide-from-output}
SED_CMDS.shell-csh= ${STD_SED_CMDS.white-space}
+SED_CMDS.sh-leading-hyphen= ${STD_SED_CMDS.shell}
SED_CMDS.suff-main+= ${STD_SED_CMDS.dg1}
SED_CMDS.suff-main-several+= ${STD_SED_CMDS.dg1}
SED_CMDS.suff-transform-debug+= ${STD_SED_CMDS.dg1}
@@ -570,17 +646,24 @@ SED_CMDS.var-op-shell+= ${STD_SED_CMDS.shell}
SED_CMDS.var-op-shell+= -e '/command/s,No such.*,not found,'
SED_CMDS.var-op-shell+= ${STD_SED_CMDS.white-space}
SED_CMDS.vardebug+= -e 's,${.SHELL},</path/to/shell>,'
+SED_CMDS.varmod-mtime+= -e "s,\(mtime for .*\): .*,\1: <ENOENT>,"
SED_CMDS.varmod-subst-regex+= ${STD_SED_CMDS.regex}
-SED_CMDS.varname-dot-parsedir= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,'
-SED_CMDS.varname-dot-parsefile= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,'
+SED_CMDS.varparse-errors+= ${STD_SED_CMDS.timestamp}
+SED_CMDS.varname-dot-make-meta-ignore_filter+= ${SED_CMDS.meta-ignore}
+SED_CMDS.varname-dot-make-meta-ignore_paths+= ${SED_CMDS.meta-ignore}
+SED_CMDS.varname-dot-make-meta-ignore_patterns+= ${SED_CMDS.meta-ignore}
+SED_CMDS.varname-dot-parsedir= -e '/in some cases/ s,^make: [^:]*:,make: <normalized>:,'
+SED_CMDS.varname-dot-parsefile= -e '/in some cases/ s,^make: [^:]*:,make: <normalized>:,'
SED_CMDS.varname-dot-shell= -e 's, = /[^ ]*, = (details omitted),g'
SED_CMDS.varname-dot-shell+= -e 's,"/[^" ]*","(details omitted)",g'
SED_CMDS.varname-dot-shell+= -e 's,\[/[^] ]*\],[(details omitted)],g'
-SED_CMDS.varname-empty= ${.OBJDIR .PARSEDIR .PATH .SHELL:L:@v@-e '/\\$v/d'@}
+SED_CMDS.varname-empty= ${.OBJDIR .PARSEDIR .PATH .SHELL .SYSPATH:L:@v@-e '/\\$v/d'@}
# Some tests need an additional round of postprocessing.
+POSTPROC.depsrc-wait= sed -e '/^---/d' -e 's,^\(: Making 3[abc]\)[123]$$,\1,'
POSTPROC.deptgt-suffixes= awk '/^\#\*\*\* Suffixes/,/^never-stop/'
-POSTPROC.gnode-submake= awk '/Input graph/, /^$$/'
+POSTPROC.gnode-submake= awk '/Begin input graph/, /^$$/'
+POSTPROC.varname-dot-make-mode= sed 's,^\(: Making [abc]\)[123]$$,\1,'
# Some tests reuse other tests, which makes them unnecessarily fragile.
export-all.rawout: export.mk
@@ -593,31 +676,32 @@ unexport-env.rawout: export.mk
# In tests that use the debugging option -dd, ignore debugging output that is
# only logged in -DCLEANUP mode.
-STD_SED_CMDS.dd= -e '/^OpenDirs_Done:/d'
-STD_SED_CMDS.dd+= -e '/^CachedDir /d'
+STD_SED_CMDS.dd= -e '/^OpenDirs_Done:/d'
+STD_SED_CMDS.dd+= -e '/^CachedDir /d'
+STD_SED_CMDS.dd+= -e 's, ${DEFSYSPATH:U/usr/share/mk} , <defsyspath> ,'
# Omit details such as process IDs from the output of the -dg1 option.
STD_SED_CMDS.dg1= -e '/\#.* \.$$/d'
STD_SED_CMDS.dg1+= -e '/\.MAKE.PATH_FILEMON/d'
-STD_SED_CMDS.dg1+= -e '/^MAKE_VERSION/d;/^\#.*\/mk/d'
+STD_SED_CMDS.dg1+= -e '/^\#.*\/mk/d'
STD_SED_CMDS.dg1+= -e 's, ${DEFSYSPATH:U/usr/share/mk}$$, <defsyspath>,'
STD_SED_CMDS.dg1+= -e 's,^\(\.MAKE *=\) .*,\1 <details omitted>,'
STD_SED_CMDS.dg1+= -e 's,^\(\.MAKE\.[A-Z_]* *=\) .*,\1 <details omitted>,'
+STD_SED_CMDS.dg1+= -e 's,^\(\.MAKE\.JOBS\.C *=\) .*,\1 <details omitted>,'
STD_SED_CMDS.dg1+= -e 's,^\(MACHINE[_ARCH]* *=\) .*,\1 <details omitted>,'
STD_SED_CMDS.dg1+= -e 's,^\(MAKE *=\) .*,\1 <details omitted>,'
STD_SED_CMDS.dg1+= -e 's,^\(\.SHELL *=\) .*,\1 <details omitted>,'
+STD_SED_CMDS.dg1+= -e '/\.SYSPATH/d'
STD_SED_CMDS.dg2= ${STD_SED_CMDS.dg1}
STD_SED_CMDS.dg2+= -e 's,\(last modified\) ..:..:.. ... ..\, ....,\1 <timestamp>,'
STD_SED_CMDS.dg3= ${STD_SED_CMDS.dg2}
# Omit details such as process IDs from the output of the -dj option.
-STD_SED_CMDS.dj= \
- -e '/Process/d;/JobFinish:/d' \
- -e 's,^\(Job_TokenWithdraw\)([0-9]*),\1(<pid>),' \
- -e 's,^([0-9][0-9]*) \(withdrew token\),(<pid>) \1,' \
- -e 's, \(pid\) [0-9][0-9]*, \1 <pid>,' \
- -e 's,^\( Command:\) .*,\1 <shell>,'
+STD_SED_CMDS.dj= -e 's, pid [0-9][0-9]*, pid <pid>,'
+STD_SED_CMDS.dj+= -e 's,^\(.Command\): ${.SHELL:T},\1: <shell>,'
+# The "-q" may be there or not, see jobs.c, variable shells.
+STD_SED_CMDS.dj+= -e 's,^\(.Command: <shell>\) -q,\1,'
# Reduce the noise for tests running with the -n option, since there is no
# other way to suppress the echoing of the commands.
@@ -649,9 +733,11 @@ STD_SED_CMDS.hide-from-output= \
# bash 5.1.0 bash: line 1: /nonexistent: No such file or directory
# dash dash: 1: cannot open /nonexistent: No such file
#
+STD_SED_CMDS.shell+= -e 's,^${.SHELL},${.SHELL:T},'
STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: line [0-9][0-9]*: ,,'
STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: [0-9][0-9]*: ,,'
STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: ,,'
+STD_SED_CMDS.shell+= -e 's,: command not found,: not found,'
STD_SED_CMDS.white-space= -e 's, *, ,g' -e 's, *$$,,'
@@ -660,6 +746,11 @@ STD_SED_CMDS.white-space= -e 's, *, ,g' -e 's, *$$,,'
STD_SED_CMDS.regex= \
-e 's,\(Regex compilation error:\).*,\1 (details omitted),'
+# Normalize timestamps from ':gmtime' or ':localtime' to '<timestamp>'.
+# See STD_SED_CMDS.dg2 for timestamps from the debug log.
+STD_SED_CMDS.timestamp= \
+ -e 's,[A-Z][a-z][a-z] [A-Z][a-z][a-z] [ 0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [12][0-9][0-9][0-9],<timestamp>,'
+
# End of the configuration helpers section.
.-include "Makefile.inc"
@@ -679,11 +770,11 @@ all: ${OUTFILES}
CLEANFILES= *.rawout *.out *.status *.tmp *.core *.tmp
CLEANFILES+= obj*.[och] lib*.a # posix1.mk
CLEANFILES+= issue* .[ab]* # suffixes.mk
-CLEANDIRS= dir dummy # posix1.mk
+CLEANDIRS= dir dummy *.tmp # posix1.mk
clean:
- rm -f ${CLEANFILES}
rm -rf ${CLEANDIRS}
+ rm -f ${CLEANFILES}
TEST_MAKE?= ${.MAKE}
TOOL_SED?= sed
@@ -691,40 +782,32 @@ TOOL_TR?= tr
TOOL_DIFF?= diff
DIFF_FLAGS?= -u
-.if defined(.PARSEDIR)
# ensure consistent results from sort(1)
LC_ALL= C
LANG= C
.export LANG LC_ALL
-.endif
.if ${.MAKE.MODE:Unormal:Mmeta} != ""
# we don't need the noise
_MKMSG_TEST= :
.endif
-
-# for many tests we need a TMPDIR that will not collide
-# with other users.
-.if ${.OBJDIR} != ${.CURDIR}
-# easy
-TMPDIR:= ${.OBJDIR}/tmp
-.elif defined(TMPDIR)
-TMPDIR:= ${TMPDIR}/uid${.MAKE.UID}
-.else
-TMPDIR:= /tmp/uid${.MAKE.UID}
-.endif
-# make sure it exists
-.if !exist(${TMPDIR})
-_!= mkdir -p ${TMPDIR}
+# Some Linux systems such as Fedora have deprecated egrep in favor of grep -E.
+.if ${.MAKE.OS:NLinux} == ""
+EGREP= grep -E
.endif
+# Keep the classical definition for all other systems. Just as the bmake code
+# is kept compatible with C90, the tests are kept compatible with systems that
+# are several decades old and don't follow modern POSIX standards.
+EGREP?= egrep
-MAKE_TEST_ENV= MALLOC_OPTIONS="JA" # for jemalloc 100
+MAKE_TEST_ENV= EGREP="${EGREP}"
+MAKE_TEST_ENV+= MALLOC_OPTIONS="JA" # for jemalloc 100
MAKE_TEST_ENV+= MALLOC_CONF="junk:true" # for jemalloc 510
MAKE_TEST_ENV+= TMPDIR=${TMPDIR}
.if ${.MAKE.OS} == "NetBSD"
-LIMIT_RESOURCES?= ulimit -v 200000
+LIMIT_RESOURCES?= ulimit -v 300000
.endif
LIMIT_RESOURCES?= :
@@ -746,42 +829,49 @@ LIMIT_RESOURCES?= :
echo $$status > ${.TARGET:R}.status
@mv ${.TARGET}.tmp ${.TARGET}
-# Postprocess the test output so that the results can be compared.
+# Postprocess the test output to make the output platform-independent.
#
-# always pretend .MAKE was called 'make'
-_SED_CMDS+= -e 's,^${TEST_MAKE:T:S,.,\\.,g}[][0-9]*:,make:,'
-_SED_CMDS+= -e 's,${TEST_MAKE:S,.,\\.,g},make,'
-_SED_CMDS+= -e 's,^usage: ${TEST_MAKE:T:S,.,\\.,g} ,usage: make ,'
-# replace anything after 'stopped in' with unit-tests
-_SED_CMDS+= -e '/stopped/s, /.*, unit-tests,'
+# Replace anything after 'stopped in' with unit-tests
+_SED_CMDS+= -e '/stopped/s, in /.*, in 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'
+_SED_CMDS+= -e 's,${TMPDIR},<tmpdir>,g' -e 's,${TMPDIR:tA},<tmpdir>,g'
# canonicalize ${.OBJDIR} and ${.CURDIR}
+_SED_CMDS+= -e 's,${.CURDIR},<curdir>,g'
.if ${.OBJDIR} != ${.CURDIR}
# yes this is inaccurate but none of the tests expect <objdir> anywhere
# which we get depending on how MAKEOBJDIR is set.
-_SED_CMDS+= -e 's,${.OBJDIR},<curdir>,g'
+_SED_CMDS+= -e 's,${.OBJDIR},<curdir>,g' -e 's,${.OBJDIR:tA},<curdir>,g'
.endif
-_SED_CMDS+= -e 's,${.CURDIR},<curdir>,g'
+# always pretend .MAKE was called 'make'
+_SED_CMDS+= -e 's,^${TEST_MAKE:T:S,.,\\.,g}[][0-9]*:,make:,'
+_SED_CMDS+= -e 's,${TEST_MAKE:S,.,\\.,g},make,'
+_SED_CMDS+= -e 's,^usage: ${TEST_MAKE:T:S,.,\\.,g} ,usage: make ,'
+_SED_CMDS+= -e 's,${TEST_MAKE:T:S,.,\\.,g}\(\[[1-9][0-9]*\][: ]\),make\1,'
_SED_CMDS+= -e 's,<curdir>/,,g'
_SED_CMDS+= -e 's,${UNIT_TESTS:S,.,\\.,g}/,,g'
+_SED_CMDS+= -e '/MAKE_VERSION/d'
+_SED_CMDS+= -e '/EGREP=/d'
+
# on AT&T derived systems: false exits 255 not 1
.if ${.MAKE.OS:N*BSD} != ""
_SED_CMDS+= -e 's,\(Error code\) 255,\1 1,'
.endif
-.if ${.SHELL:T} == "ksh"
+.if ${_shell:N*ksh*} == ""
_SED_CMDS+= -e '/^set [+-]v/d'
+SED_CMDS.opt-debug-jobs+= -e 's,Command: ksh -v,Command: <shell>,'
+SED_CMDS.opt-debug-jobs+= -e 's,Command: <shell> -v,Command: <shell>,'
.endif
.rawout.out:
@${TOOL_SED} ${_SED_CMDS} ${SED_CMDS.${.PREFIX:T}} \
- < ${.IMPSRC} > ${.TARGET}.tmp1
- @${POSTPROC.${.PREFIX:T}:Ucat} < ${.TARGET}.tmp1 > ${.TARGET}.tmp2
- @rm ${.TARGET}.tmp1
- @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp2
- @mv ${.TARGET}.tmp2 ${.TARGET}
+ < ${.IMPSRC} > ${.TARGET}.tmp
+ @${POSTPROC.${.PREFIX:T}:D \
+ ${POSTPROC.${.PREFIX:T}} < ${.TARGET}.tmp > ${.TARGET}.post \
+ && mv ${.TARGET}.post ${.TARGET}.tmp}
+ @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp
+ @mv ${.TARGET}.tmp ${.TARGET}
.if empty(DIFF_FLAGS)
DIFF_ECHO= echo
@@ -802,6 +892,11 @@ test: ${OUTFILES} .PHONY
echo "Failed tests: $${failed}" ; false ; \
else \
echo "All tests passed" ; \
+ lua=${LUA:Ulua} ; \
+ have_lua=$$("$$lua" -e 'print "yes"' 2>&1) ; \
+ if [ "$$have_lua" = "yes" -a -s ${.CURDIR}/check-expect.lua ]; then \
+ (cd ${.CURDIR} && "$$lua" ./check-expect.lua *.mk); \
+ fi; \
fi
accept:
diff --git a/contrib/bmake/unit-tests/Makefile.config.in b/contrib/bmake/unit-tests/Makefile.config.in
index 3139a0d4d0b5..30049eaa7c26 100644
--- a/contrib/bmake/unit-tests/Makefile.config.in
+++ b/contrib/bmake/unit-tests/Makefile.config.in
@@ -1,6 +1,7 @@
-# $Id: Makefile.config.in,v 1.3 2021/10/22 07:48:57 sjg Exp $
+# $Id: Makefile.config.in,v 1.4 2022/09/09 18:44:56 sjg Exp $
srcdir= @srcdir@
+EGREP= @egrep@
TOOL_DIFF?= @diff@
DIFF_FLAGS?= @diff_u@
UTC_1= @UTC_1@
diff --git a/contrib/bmake/unit-tests/archive.exp b/contrib/bmake/unit-tests/archive.exp
index 645add4f5899..ea81a3589ff8 100644
--- a/contrib/bmake/unit-tests/archive.exp
+++ b/contrib/bmake/unit-tests/archive.exp
@@ -18,6 +18,18 @@ list-archive-wildcard: archive-suffix.mk
list-archive-wildcard: archive.mk
list-archive-wildcard: ternary.mk
+make: archive.mk:61: Error in source archive spec "libprog.a${UNDEF}(archive.mk) pre post"
+ in make[1] in directory "<curdir>"
+make: Fatal errors encountered -- cannot continue
+make: stopped making "list-archive-undef-archive" in unit-tests
+exit 1
+
+make: archive.mk:68: Error in source archive spec "libprog.a"
+ in make[1] in directory "<curdir>"
+make: Fatal errors encountered -- cannot continue
+make: stopped making "list-archive-undef-member" in unit-tests
+exit 1
+
Making depend-on-existing-member out-of-date archive.mk
depend-on-existing-member
@@ -25,4 +37,12 @@ depend-on-existing-member
Making remove-archive
rm -f libprog.a
+begin library
+Examining libbad.a...up-to-date.
+Examining -lbad...up-to-date.
+Examining libgood.a...library...up-to-date.
+Examining -lgood...library...up-to-date.
+Examining library...nonexistent....PHONY node...out-of-date.
+Examining .END...nonexistent...nonexistent and no sources...out-of-date.
+end library
exit status 0
diff --git a/contrib/bmake/unit-tests/archive.mk b/contrib/bmake/unit-tests/archive.mk
index 2cd43a99e9ad..f19c63f29632 100644
--- a/contrib/bmake/unit-tests/archive.mk
+++ b/contrib/bmake/unit-tests/archive.mk
@@ -1,4 +1,4 @@
-# $NetBSD: archive.mk,v 1.12 2021/04/09 14:42:00 christos Exp $
+# $NetBSD: archive.mk,v 1.14 2025/01/10 23:00:38 rillig Exp $
#
# Very basic demonstration of handling archives, based on the description
# in PSD.doc/tutorial.ms.
@@ -21,9 +21,19 @@ all:
@${MAKE} -f ${MAKEFILE} create-archive
@${MAKE} -f ${MAKEFILE} list-archive
@${MAKE} -f ${MAKEFILE} list-archive-wildcard
+ @${MAKE} -f ${MAKEFILE} list-archive-undef-archive || echo "exit $$?"
+ @echo
+ @${MAKE} -f ${MAKEFILE} list-archive-undef-member || echo "exit $$?"
+ @echo
@${MAKE} -f ${MAKEFILE} depend-on-existing-member
@${MAKE} -f ${MAKEFILE} depend-on-nonexistent-member
@${MAKE} -f ${MAKEFILE} remove-archive
+ @${MAKE} -f ${MAKEFILE} set-up-library
+ @${MAKE} -f ${MAKEFILE} -dm library 2>&1 \
+ | sed -n '/^Examining/p' \
+ | sed 's,\.\.\.modified[^.]*,,'
+ @${MAKE} -f ${MAKEFILE} tear-down-library
+
create-archive: ${ARCHIVE} pre post
@@ -45,6 +55,20 @@ list-archive: ${ARCHIVE} pre post
list-archive-wildcard: ${ARCHIVE}([at]*.mk) pre post
@printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@}
+.if make(list-archive-undef-archive)
+# TODO: Be more specific: mention that the variable "UNDEF" is not defined.
+# expect+1: Error in source archive spec "libprog.a${UNDEF}(archive.mk) pre post"
+list-archive-undef-archive: ${ARCHIVE}$${UNDEF}(archive.mk) pre post
+ @printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@}
+.endif
+
+.if make(list-archive-undef-member)
+# TODO: Be more specific: mention that the variable "UNDEF" is not defined.
+# expect+1: Error in source archive spec "libprog.a"
+list-archive-undef-member: ${ARCHIVE}(archive$${UNDEF}.mk) pre post
+ @printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@}
+.endif
+
depend-on-existing-member: ${ARCHIVE}(archive.mk) pre post
@echo $@
@@ -58,3 +82,28 @@ pre: .USEBEFORE
@echo Making ${.TARGET} ${.OODATE:C,.+,out-of-date,W} ${.OODATE:O}
post: .USE
@echo
+
+
+set-up-library: .PHONY
+ @echo "member" > member.txt
+ @echo "not a library" > libbad.a
+ @ar cr libgood.a member.txt
+ @echo "begin library"
+
+.if make(library)
+.SUFFIXES: .a
+.LIBS: .a
+.endif
+# The two lines for libgood contain the word "library", the two lines for
+# libbad don't.
+#
+# expect: Examining libbad.a...up-to-date.
+# expect: Examining -lbad...up-to-date.
+# expect: Examining libgood.a...library...up-to-date.
+# expect: Examining -lgood...library...up-to-date.
+library: .PHONY libbad.a -lbad libgood.a -lgood
+ : Making ${.TARGET} from ${.ALLSRC}
+
+tear-down-library: .PHONY
+ @echo "end library"
+ @rm member.txt libbad.a libgood.a
diff --git a/contrib/bmake/unit-tests/char-005c-reverse-solidus.exp b/contrib/bmake/unit-tests/char-005c-reverse-solidus.exp
new file mode 100644
index 000000000000..351ef95040e5
--- /dev/null
+++ b/contrib/bmake/unit-tests/char-005c-reverse-solidus.exp
@@ -0,0 +1,13 @@
+make: char-005c-reverse-solidus.mk:57: Unclosed expression, expecting "}" for modifier "Mx\}"
+ while evaluating variable "BACKSLASH" with value ""
+make: char-005c-reverse-solidus.mk:64: Unclosed expression, expecting "}" for modifier "Mx\\}"
+ while evaluating variable "BACKSLASH" with value ""
+make: char-005c-reverse-solidus.mk:71: Unclosed expression, expecting "}" for modifier "Mx\\\\\\\\}"
+ while evaluating variable "BACKSLASH" with value ""
+make: char-005c-reverse-solidus.mk:100: Unfinished backslash at the end in pattern "\" of modifier ":M"
+ while evaluating variable "BACKSLASH" with value "\"
+make: char-005c-reverse-solidus.mk:111: Unclosed expression, expecting "}" for modifier "M${:U\\\\}} != "\\""
+ while evaluating variable "BACKSLASH" with value ""
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/char-005c-reverse-solidus.mk b/contrib/bmake/unit-tests/char-005c-reverse-solidus.mk
new file mode 100644
index 000000000000..7a151ac46322
--- /dev/null
+++ b/contrib/bmake/unit-tests/char-005c-reverse-solidus.mk
@@ -0,0 +1,131 @@
+# $NetBSD: char-005c-reverse-solidus.mk,v 1.2 2025/06/29 11:27:21 rillig Exp $
+#
+# Tests for the character U+005C "REVERSE SOLIDUS".
+#
+# See also:
+# TODO
+# TODO
+# TODO
+
+# TODO: Where is this character used normally?
+# TODO: What are the edge cases?
+
+# TODO: escape '#' in lines
+# TODO: escape '#' in comments
+# TODO: escape ':' in modifiers
+# TODO: escape any character in condition strings
+
+# begin https://gnats.netbsd.org/46139
+
+# Too see the details of parsing, uncomment the following line.
+#.MAKEFLAGS: -dcpv
+
+# This backslash is treated as a line continuation.
+# It does not end up in the variable value.
+LINE_CONTINUATION=foo\
+# This line is still part of the variable assignment
+.if ${LINE_CONTINUATION:C,[^a-z],<>,gW} != "foo"
+. error
+.endif
+
+# The variable value contains two backslashes.
+TWO_BACKSLASHES_AT_EOL=foo\\
+.if ${TWO_BACKSLASHES_AT_EOL:C,[^a-z],<>,gW} != "foo<><>"
+. error
+.endif
+
+TRAILING_WHITESPACE=foo\ # trailing space
+.if ${TRAILING_WHITESPACE:C,[^a-z],<>,gW} != "foo<><>"
+. error
+.endif
+
+# The simplest was to produce a single backslash is the :U modifier.
+BACKSLASH= ${:U\\}
+.if ${BACKSLASH} != "\\"
+. error
+.endif
+BACKSLASH_C= ${:U1:C,.,\\,}
+.if ${BACKSLASH_C} != "\\"
+. error
+.endif
+
+# expect+5: Unclosed expression, expecting "}" for modifier "Mx\}"
+# At the point where the unclosed expression is detected, the ":M" modifier
+# has been applied to the expression. Its pattern is "x}", which doesn't
+# match the single backslash.
+# expect: while evaluating variable "BACKSLASH" with value ""
+.if ${BACKSLASH:Mx\}
+. error
+.else
+. error
+.endif
+
+# expect+1: Unclosed expression, expecting "}" for modifier "Mx\\}"
+.if ${BACKSLASH:Mx\\}
+. error
+.else
+. error
+.endif
+
+# expect+1: Unclosed expression, expecting "}" for modifier "Mx\\\\\\\\}"
+.if ${BACKSLASH:Mx\\\\\\\\}
+. error
+.else
+. error
+.endif
+
+# Adding more text after the backslash adds to the pattern, as the backslash
+# serves to escape the ":" that is otherwise used to separate the modifiers.
+# The result is a single ":M" modifier with the pattern "x:Nzzz".
+.if ${BACKSLASH:Mx\:Nzzz} != ""
+. error
+.endif
+
+# The pattern ends up as "x\:Nzzz". Only the sequence "\:" is unescaped, all
+# others, including "\\", are left as-is.
+.if ${BACKSLASH:Mx\\:Nzzz} != ""
+. error
+.endif
+
+# The pattern for the ":M" modifier ends up as "x\\\\\\\:Nzzz". Only the
+# sequence "\:" is unescaped, all others, including "\\", are left as-is.
+.if ${BACKSLASH:Mx\\\\\\\\:Nzzz} != ""
+. error
+.endif
+
+# The ":M" modifier is parsed differently than the other modifiers. To
+# circumvent the peculiarities of that parser, the pattern can be passed via
+# an expression. There, the usual escaping rules for modifiers apply.
+# expect+1: Unfinished backslash at the end in pattern "\" of modifier ":M"
+.if ${BACKSLASH:M${BACKSLASH}} != "\\"
+. error
+.else
+. error
+.endif
+
+# Trying to bypass the parser by using a direct expression doesn't work, as
+# the parser for the ":M" modifier does not parse the subexpression like in
+# all other places, but instead counts the braces and tries to decode the
+# escaping, which fails in this case.
+# expect+1: Unclosed expression, expecting "}" for modifier "M${:U\\\\}} != "\\""
+.if ${BACKSLASH:M${:U\\\\}} != "\\"
+. error
+.else
+. error
+.endif
+
+# Matching a backslash with the pattern matching characters works.
+.if ${BACKSLASH:M?} != "\\"
+. error
+.endif
+.if ${BACKSLASH:M*} != "\\"
+. error
+.endif
+.if ${BACKSLASH:M[Z-a]} != "\\"
+. error
+.endif
+.if ${BACKSLASH:M[\\]} != "\\"
+. error
+.endif
+
+# end https://gnats.netbsd.org/46139
diff --git a/contrib/bmake/unit-tests/check-expect.lua b/contrib/bmake/unit-tests/check-expect.lua
new file mode 100644
index 000000000000..2f3adf49baa6
--- /dev/null
+++ b/contrib/bmake/unit-tests/check-expect.lua
@@ -0,0 +1,189 @@
+#! /usr/bin/lua
+-- $NetBSD: check-expect.lua,v 1.17 2025/07/01 05:03:18 rillig Exp $
+
+--[[
+
+usage: lua ./check-expect.lua *.mk
+
+Check that the various 'expect' comments in the .mk files produce the
+expected text in the corresponding .exp file.
+
+# expect: <line>
+ Each <line> must occur in the .exp file.
+ The order in the .mk file must be the same as in the .exp file.
+
+# expect[+-]offset: <message>
+ Each <message> must occur in the .exp file and refer back to the
+ source line in the .mk file.
+ Each such line in the .exp file must have a corresponding expect line
+ in the .mk file.
+ The order in the .mk file must be the same as in the .exp file.
+
+# expect-reset
+ Search the following "expect:" and "expect[+-]offset:" comments
+ from the top of the .exp file again.
+
+# expect-not: <substring>
+ The <substring> must not occur as part of any line in the .exp file.
+
+# expect-not-matches: <pattern>
+ The <pattern> (see https://lua.org/manual/5.4/manual.html#6.4.1)
+ must not occur as part of any line in the .exp file.
+]]
+
+
+local had_errors = false
+---@param fmt string
+local function print_error(fmt, ...)
+ print(fmt:format(...))
+ had_errors = true
+end
+
+
+---@return nil | string[]
+local function load_lines(fname)
+ local lines = {}
+
+ local f = io.open(fname, "r")
+ if f == nil then
+ return nil
+ end
+
+ for line in f:lines() do
+ table.insert(lines, line)
+ end
+ f:close()
+
+ return lines
+end
+
+
+--- @shape ExpLine
+--- @field filename string | nil
+--- @field lineno number | nil
+--- @field text string
+
+
+--- @param lines string[]
+--- @return ExpLine[]
+local function parse_exp(lines)
+ local exp_lines = {}
+ for _, line in ipairs(lines) do
+ local l_filename, l_lineno, l_text =
+ line:match('^make: ([^:]+%.mk):(%d+):%s+(.*)')
+ if not l_filename then
+ l_text = line
+ end
+ l_text = l_text:gsub("^%s+", ""):gsub("%s+$", "")
+ table.insert(exp_lines, {
+ filename = l_filename,
+ lineno = tonumber(l_lineno),
+ text = l_text,
+ })
+ end
+ return exp_lines
+end
+
+---@param exp_lines ExpLine[]
+local function detect_missing_expect_lines(exp_fname, exp_lines, s, e)
+ for i = s, e do
+ local exp_line = exp_lines[i]
+ if exp_line.filename then
+ print_error("error: %s:%d requires in %s:%d: # expect+1: %s",
+ exp_fname, i, exp_line.filename, exp_line.lineno, exp_line.text)
+ end
+ end
+end
+
+local function check_mk(mk_fname)
+ local exp_fname = mk_fname:gsub("%.mk$", ".exp")
+ local mk_lines = load_lines(mk_fname)
+ local exp_raw_lines = load_lines(exp_fname)
+ if exp_raw_lines == nil then
+ return
+ end
+ local exp_lines = parse_exp(exp_raw_lines)
+
+ local exp_it = 1
+
+ for mk_lineno, mk_line in ipairs(mk_lines) do
+
+ local function match(pattern, action)
+ local _, n = mk_line:gsub(pattern, action)
+ if n > 0 then
+ match = function() end
+ end
+ end
+
+ match("^#%s+expect%-not:%s*(.*)", function(text)
+ for exp_lineno, exp_line in ipairs(exp_lines) do
+ if exp_line.text:find(text, 1, true) then
+ print_error("error: %s:%d: %s:%d must not contain '%s'",
+ mk_fname, mk_lineno, exp_fname, exp_lineno, text)
+ end
+ end
+ end)
+
+ match("^#%s+expect%-not%-matches:%s*(.*)", function(pattern)
+ for exp_lineno, exp_line in ipairs(exp_lines) do
+ if exp_line.text:find(pattern) then
+ print_error("error: %s:%d: %s:%d must not match '%s'",
+ mk_fname, mk_lineno, exp_fname, exp_lineno, pattern)
+ end
+ end
+ end)
+
+ match("^#%s+expect:%s*(.*)", function(text)
+ local i = exp_it
+ while i <= #exp_lines and text ~= exp_lines[i].text do
+ i = i + 1
+ end
+ if i <= #exp_lines then
+ detect_missing_expect_lines(exp_fname, exp_lines, exp_it, i - 1)
+ exp_lines[i].text = ""
+ exp_it = i + 1
+ else
+ print_error("error: %s:%d: '%s:%d+' must contain '%s'",
+ mk_fname, mk_lineno, exp_fname, exp_it, text)
+ end
+ end)
+
+ match("^#%s+expect%-reset$", function()
+ exp_it = 1
+ end)
+
+ match("^#%s+expect([+%-]%d+):%s*(.*)", function(offset, text)
+ local msg_lineno = mk_lineno + tonumber(offset)
+
+ local i = exp_it
+ while i <= #exp_lines and text ~= exp_lines[i].text do
+ i = i + 1
+ end
+
+ if i <= #exp_lines and exp_lines[i].lineno == msg_lineno then
+ detect_missing_expect_lines(exp_fname, exp_lines, exp_it, i - 1)
+ exp_lines[i].text = ""
+ exp_it = i + 1
+ elseif i <= #exp_lines then
+ print_error("error: %s:%d: expect%+d must be expect%+d",
+ mk_fname, mk_lineno, tonumber(offset),
+ exp_lines[i].lineno - mk_lineno)
+ else
+ print_error("error: %s:%d: %s:%d+ must contain '%s'",
+ mk_fname, mk_lineno, exp_fname, exp_it, text)
+ end
+ end)
+
+ match("^#%s+expect[+%-:]", function()
+ print_error("error: %s:%d: invalid \"expect\" line: %s",
+ mk_fname, mk_lineno, mk_line)
+ end)
+
+ end
+ detect_missing_expect_lines(exp_fname, exp_lines, exp_it, #exp_lines)
+end
+
+for _, fname in ipairs(arg) do
+ check_mk(fname)
+end
+os.exit(not had_errors)
diff --git a/contrib/bmake/unit-tests/cmd-errors-jobs.exp b/contrib/bmake/unit-tests/cmd-errors-jobs.exp
index 9ed0557975b3..3be1bb9b0773 100644
--- a/contrib/bmake/unit-tests/cmd-errors-jobs.exp
+++ b/contrib/bmake/unit-tests/cmd-errors-jobs.exp
@@ -1,9 +1,79 @@
-: undefined eol
+begin undefined-direct
+: undefined-direct--eol
+end undefined-direct with status 0
+
+begin undefined-indirect
+: undefined-direct--eol
+end undefined-indirect with status 0
+
+begin parse-error-direct
make: Unclosed variable "UNCLOSED"
-: unclosed-variable
-make: Unclosed variable expression (expecting '}') for "UNCLOSED"
-: unclosed-modifier
-make: Unknown modifier "Z"
-: unknown-modifier eol
-: end eol
+ in command ": unexpected $@-${UNCLOSED"
+ in target "parse-error-unclosed-expression"
+ in make[1] in directory "<curdir>"
+make: Unclosed expression, expecting "}"
+ while evaluating variable "UNCLOSED" with value ""
+ in command ": unexpected $@-${UNCLOSED:"
+ in target "parse-error-unclosed-modifier"
+ in make[1] in directory "<curdir>"
+make: Unknown modifier ":Z"
+ while evaluating variable "UNKNOWN" with value ""
+ in command ": unexpected $@-${UNKNOWN:Z}-eol"
+ in target "parse-error-unknown-modifier"
+ in make[1] in directory "<curdir>"
+end parse-error-direct with status 2
+
+begin parse-error-indirect
+make: Unclosed variable "UNCLOSED"
+ in command ": unexpected $@-${UNCLOSED"
+ in target "parse-error-unclosed-expression"
+ in make[1] in directory "<curdir>"
+make: Unclosed expression, expecting "}"
+ while evaluating variable "UNCLOSED" with value ""
+ in command ": unexpected $@-${UNCLOSED:"
+ in target "parse-error-unclosed-modifier"
+ in make[1] in directory "<curdir>"
+make: Unknown modifier ":Z"
+ while evaluating variable "UNKNOWN" with value ""
+ in command ": unexpected $@-${UNKNOWN:Z}-eol"
+ in target "parse-error-unknown-modifier"
+ in make[1] in directory "<curdir>"
+end parse-error-indirect with status 2
+
+begin begin-direct
+(exit 13) # .BEGIN
+*** Error code 13 (continuing)
+
+
+Stop.
+make: stopped making "begin-direct" in unit-tests
+end begin-direct with status 1
+
+begin begin-indirect
+(exit 13) # before-begin
+*** Error code 13 (continuing)
+
+
+Stop.
+make: stopped making "begin-indirect" in unit-tests
+end begin-indirect with status 1
+
+begin end-direct
+(exit 13) # .END
+*** Error code 13 (continuing)
+
+
+Stop.
+make: stopped making "end-direct" in unit-tests
+end end-direct with status 1
+
+begin end-indirect
+(exit 13) # before-end
+*** Error code 13 (continuing)
+
+
+Stop.
+make: stopped making "end-indirect" in unit-tests
+end end-indirect with status 1
+
exit status 0
diff --git a/contrib/bmake/unit-tests/cmd-errors-jobs.mk b/contrib/bmake/unit-tests/cmd-errors-jobs.mk
index 8462a2e3497e..f6261d6ad1a8 100644
--- a/contrib/bmake/unit-tests/cmd-errors-jobs.mk
+++ b/contrib/bmake/unit-tests/cmd-errors-jobs.mk
@@ -1,32 +1,106 @@
-# $NetBSD: cmd-errors-jobs.mk,v 1.1 2020/12/27 05:11:40 rillig Exp $
+# $NetBSD: cmd-errors-jobs.mk,v 1.16 2025/06/28 22:39:28 rillig Exp $
#
-# Demonstrate how errors in variable expansions affect whether the commands
+# Demonstrate how errors in expressions affect whether the commands
# are actually executed in jobs mode.
-.MAKEFLAGS: -j1
+RUN= @ run() { \
+ echo "begin $$*" \
+ && ${MAKE} -f ${MAKEFILE} -j1 "$$*" \
+ && echo "end $$* with status $$?" \
+ || echo "end $$* with status $$?" \
+ && echo; \
+ } && run
-all: undefined unclosed-variable unclosed-modifier unknown-modifier end
+all:
+ ${RUN} undefined-direct
+ ${RUN} undefined-indirect
+ ${RUN} parse-error-direct
+ ${RUN} parse-error-indirect
+ ${RUN} begin-direct
+ ${RUN} begin-indirect
+ ${RUN} end-direct
+ ${RUN} end-indirect
-# Undefined variables are not an error. They expand to empty strings.
-undefined:
- : $@ ${UNDEFINED} eol
-# XXX: As of 2020-11-01, this command is executed even though it contains
-# parse errors.
-unclosed-variable:
- : $@ ${UNCLOSED
+# Undefined variables in expressions are not an error. They expand to empty
+# strings.
+# expect: : undefined-direct--eol
+# expect: end undefined-direct with status 0
+# expect: : undefined-direct--eol
+# expect: end undefined-indirect with status 0
+undefined-indirect: undefined-direct
+undefined-direct:
+ : $@-${UNDEFINED}-eol
-# XXX: As of 2020-11-01, this command is executed even though it contains
-# parse errors.
-unclosed-modifier:
- : $@ ${UNCLOSED:
-# XXX: As of 2020-11-01, this command is executed even though it contains
-# parse errors.
-unknown-modifier:
- : $@ ${UNKNOWN:Z} eol
+parse-error-indirect: parse-error-direct
+parse-error-direct: parse-error-unclosed-expression
+parse-error-direct: parse-error-unclosed-modifier
+parse-error-direct: parse-error-unknown-modifier
-end:
- : $@ eol
+parse-error-unclosed-expression:
+ : unexpected $@-${UNCLOSED
-# XXX: As of 2020-11-02, despite the parse errors, the exit status is 0.
+parse-error-unclosed-modifier:
+ : unexpected $@-${UNCLOSED:
+
+parse-error-unknown-modifier:
+ : unexpected $@-${UNKNOWN:Z}-eol
+
+# expect-not-matches: ^: unexpected
+# expect: make: Unclosed variable "UNCLOSED"
+# expect: in command ": unexpected $@-${UNCLOSED"
+# expect: make: Unclosed expression, expecting "}"
+# expect: make: Unknown modifier ":Z"
+# expect: end parse-error-direct with status 2
+# expect: make: Unclosed variable "UNCLOSED"
+# expect: make: Unclosed expression, expecting "}"
+# expect: make: Unknown modifier ":Z"
+# expect: end parse-error-indirect with status 2
+
+
+.if make(begin-direct)
+begin-direct:
+.BEGIN:
+ (exit 13) # $@
+.endif
+# expect: begin begin-direct
+# expect: make: stopped making "begin-direct" in unit-tests
+# expect: end begin-direct with status 1
+
+
+.if make(begin-indirect)
+begin-indirect:
+.BEGIN: before-begin
+ : Making $@
+before-begin:
+ (exit 13) # $@
+.endif
+# expect: begin begin-indirect
+# expect: *** Error code 13 (continuing)
+# expect: make: stopped making "begin-indirect" in unit-tests
+# expect: end begin-indirect with status 1
+
+
+.if make(end-direct)
+end-direct:
+.END:
+ (exit 13) # $@
+.endif
+# expect: begin end-direct
+# expect: *** Error code 13 (continuing)
+# expect: Stop.
+# expect: make: stopped making "end-direct" in unit-tests
+# expect: end end-direct with status 1
+
+.if make(end-indirect)
+end-indirect:
+.END: before-end
+ : Making $@
+before-end:
+ (exit 13) # $@
+.endif
+# expect: begin end-indirect
+# expect: *** Error code 13 (continuing)
+# expect: make: stopped making "end-indirect" in unit-tests
+# expect: end end-indirect with status 1
diff --git a/contrib/bmake/unit-tests/cmd-errors-lint.exp b/contrib/bmake/unit-tests/cmd-errors-lint.exp
index 90b63bbcb08e..b08a65ff96dc 100644
--- a/contrib/bmake/unit-tests/cmd-errors-lint.exp
+++ b/contrib/bmake/unit-tests/cmd-errors-lint.exp
@@ -1,9 +1,14 @@
: undefined
make: Unclosed variable "UNCLOSED"
-: unclosed-variable
-make: Unclosed variable expression (expecting '}') for "UNCLOSED"
-: unclosed-modifier
-make: Unknown modifier "Z"
-: unknown-modifier
+ in command ": $@ ${UNCLOSED"
+ in target "unclosed-expression"
+make: Unclosed expression, expecting "}"
+ while evaluating variable "UNCLOSED" with value ""
+ in command ": $@ ${UNCLOSED:"
+ in target "unclosed-modifier"
+make: Unknown modifier ":Z"
+ while evaluating variable "UNKNOWN" with value ""
+ in command ": $@ ${UNKNOWN:Z}"
+ in target "unknown-modifier"
: end
exit status 2
diff --git a/contrib/bmake/unit-tests/cmd-errors-lint.mk b/contrib/bmake/unit-tests/cmd-errors-lint.mk
index 371e12af0f4f..455ef2c0cfe7 100644
--- a/contrib/bmake/unit-tests/cmd-errors-lint.mk
+++ b/contrib/bmake/unit-tests/cmd-errors-lint.mk
@@ -1,32 +1,35 @@
-# $NetBSD: cmd-errors-lint.mk,v 1.1 2020/11/02 20:43:27 rillig Exp $
+# $NetBSD: cmd-errors-lint.mk,v 1.8 2025/06/28 22:39:28 rillig Exp $
#
-# Demonstrate how errors in variable expansions affect whether the commands
+# Demonstrate how errors in expressions affect whether the commands
# are actually executed.
.MAKEFLAGS: -dL
-all: undefined unclosed-variable unclosed-modifier unknown-modifier end
+all: undefined unclosed-expression unclosed-modifier unknown-modifier end
-# Undefined variables are not an error. They expand to empty strings.
+# Undefined variables in expressions are not an error. They expand to empty
+# strings.
undefined:
+# expect: : undefined
: $@ ${UNDEFINED}
-# XXX: As of 2020-11-01, this obvious syntax error is not detected.
-# XXX: As of 2020-11-01, this command is executed even though it contains
-# parse errors.
-unclosed-variable:
+unclosed-expression:
+# expect: make: Unclosed variable "UNCLOSED"
+# expect-not: : unclosed-expression
: $@ ${UNCLOSED
-# XXX: As of 2020-11-01, this obvious syntax error is not detected.
-# XXX: As of 2020-11-01, this command is executed even though it contains
-# parse errors.
unclosed-modifier:
+# expect: make: Unclosed expression, expecting "}"
+# expect-not: : unclosed-modifier
: $@ ${UNCLOSED:
-# XXX: As of 2020-11-01, this command is executed even though it contains
-# parse errors.
unknown-modifier:
+# expect: make: Unknown modifier ":Z"
+# expect-not: : unknown-modifier
: $@ ${UNKNOWN:Z}
end:
+# expect: : end
: $@
+
+# expect: exit status 2
diff --git a/contrib/bmake/unit-tests/cmd-errors.exp b/contrib/bmake/unit-tests/cmd-errors.exp
index 9ed0557975b3..2916d423029e 100644
--- a/contrib/bmake/unit-tests/cmd-errors.exp
+++ b/contrib/bmake/unit-tests/cmd-errors.exp
@@ -1,9 +1,14 @@
-: undefined eol
+: undefined--eol
make: Unclosed variable "UNCLOSED"
-: unclosed-variable
-make: Unclosed variable expression (expecting '}') for "UNCLOSED"
-: unclosed-modifier
-make: Unknown modifier "Z"
-: unknown-modifier eol
-: end eol
-exit status 0
+ in command ": $@-${UNCLOSED"
+ in target "unclosed-expression"
+make: Unclosed expression, expecting "}"
+ while evaluating variable "UNCLOSED" with value ""
+ in command ": $@-${UNCLOSED:"
+ in target "unclosed-modifier"
+make: Unknown modifier ":Z"
+ while evaluating variable "UNKNOWN" with value ""
+ in command ": $@-${UNKNOWN:Z}-eol"
+ in target "unknown-modifier"
+: end-eol
+exit status 2
diff --git a/contrib/bmake/unit-tests/cmd-errors.mk b/contrib/bmake/unit-tests/cmd-errors.mk
index 356fe1a3e4a2..52e314c5bd38 100644
--- a/contrib/bmake/unit-tests/cmd-errors.mk
+++ b/contrib/bmake/unit-tests/cmd-errors.mk
@@ -1,30 +1,33 @@
-# $NetBSD: cmd-errors.mk,v 1.4 2020/12/27 05:11:40 rillig Exp $
+# $NetBSD: cmd-errors.mk,v 1.13 2025/06/28 22:39:28 rillig Exp $
#
-# Demonstrate how errors in variable expansions affect whether the commands
+# Demonstrate how errors in expressions affect whether the commands
# are actually executed in compat mode.
-all: undefined unclosed-variable unclosed-modifier unknown-modifier end
+all: undefined unclosed-expression unclosed-modifier unknown-modifier end
-# Undefined variables are not an error. They expand to empty strings.
+# Undefined variables in expressions are not an error. They expand to empty
+# strings.
undefined:
- : $@ ${UNDEFINED} eol
+# expect: : undefined--eol
+ : $@-${UNDEFINED}-eol
-# XXX: As of 2020-11-01, this command is executed even though it contains
-# parse errors.
-unclosed-variable:
- : $@ ${UNCLOSED
+unclosed-expression:
+# expect: make: Unclosed variable "UNCLOSED"
+# expect-not: : unclosed-expression-
+ : $@-${UNCLOSED
-# XXX: As of 2020-11-01, this command is executed even though it contains
-# parse errors.
unclosed-modifier:
- : $@ ${UNCLOSED:
+# expect: make: Unclosed expression, expecting "}"
+# expect-not: : unclosed-modifier-
+ : $@-${UNCLOSED:
-# XXX: As of 2020-11-01, this command is executed even though it contains
-# parse errors.
unknown-modifier:
- : $@ ${UNKNOWN:Z} eol
+# expect: make: Unknown modifier ":Z"
+# expect-not: : unknown-modifier--eol
+ : $@-${UNKNOWN:Z}-eol
end:
- : $@ eol
+# expect: : end-eol
+ : $@-eol
-# XXX: As of 2020-11-02, despite the parse errors, the exit status is 0.
+# expect: exit status 2
diff --git a/contrib/bmake/unit-tests/cmd-interrupt.exp b/contrib/bmake/unit-tests/cmd-interrupt.exp
index 91f4439e7bea..e03b64870fa3 100755
--- a/contrib/bmake/unit-tests/cmd-interrupt.exp
+++ b/contrib/bmake/unit-tests/cmd-interrupt.exp
@@ -2,8 +2,8 @@
make: *** cmd-interrupt-ordinary removed
interrupt-ordinary: ok
> cmd-interrupt-phony
-make: *** cmd-interrupt-phony removed
interrupt-phony: ok
> cmd-interrupt-precious
interrupt-precious: ok
+interrupt-compat expected-fail
exit status 0
diff --git a/contrib/bmake/unit-tests/cmd-interrupt.mk b/contrib/bmake/unit-tests/cmd-interrupt.mk
index fa0d85fc9063..7ee53c6c2105 100755
--- a/contrib/bmake/unit-tests/cmd-interrupt.mk
+++ b/contrib/bmake/unit-tests/cmd-interrupt.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cmd-interrupt.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $
+# $NetBSD: cmd-interrupt.mk,v 1.5 2024/07/13 15:10:06 rillig Exp $
#
# Tests for interrupting a command.
#
@@ -17,10 +17,16 @@
# See also:
# CompatDeleteTarget
-all: clean-before interrupt-ordinary interrupt-phony interrupt-precious clean-after
+all: clean-before
+all: interrupt-ordinary
+all: interrupt-phony
+all: interrupt-precious
+all: interrupt-compat
+all: clean-after
clean-before clean-after: .PHONY
- @rm -f cmd-interrupt-ordinary cmd-interrupt-phony cmd-interrupt-precious
+ @rm -f cmd-interrupt-ordinary cmd-interrupt-phony
+ @rm -f cmd-interrupt-precious cmd-interrupt-compat
interrupt-ordinary:
@${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-ordinary || true
@@ -30,13 +36,17 @@ interrupt-ordinary:
interrupt-phony: .PHONY
@${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-phony || true
# The ././ is necessary to work around the file cache.
- @echo ${.TARGET}: ${exists(././cmd-interrupt-phony) :? error : ok }
+ @echo ${.TARGET}: ${exists(././cmd-interrupt-phony) :? ok : error }
interrupt-precious: .PRECIOUS
@${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-precious || true
# The ././ is necessary to work around the file cache.
@echo ${.TARGET}: ${exists(././cmd-interrupt-precious) :? ok : error }
+interrupt-compat:
+ @${MAKE} -f ${MAKEFILE} cmd-interrupt-compat || true
+ @echo ${.TARGET} ${exists(././cmd-interrupt-compat) :? expected-fail : unexpected-ok }
+
cmd-interrupt-ordinary:
> ${.TARGET}
@kill -INT ${.MAKE.PID}
@@ -48,3 +58,11 @@ cmd-interrupt-phony: .PHONY
cmd-interrupt-precious: .PRECIOUS
> ${.TARGET}
@kill -INT ${.MAKE.PID}
+
+# When the make process (and not the process group) is interrupted in compat
+# mode, it first tries to interrupt the process group of the currently running
+# child command, but that fails since there is no such process group, rather
+# the child command runs in the same process group as make itself. The child
+# command then continues, and after sleeping a bit creates the target file.
+cmd-interrupt-compat:
+ @kill -INT ${.MAKE.PID} && sleep 1 && > ${.TARGET}
diff --git a/contrib/bmake/unit-tests/cmdline-undefined.exp b/contrib/bmake/unit-tests/cmdline-undefined.exp
index 977ceee6dbf5..ae0b35dac420 100644
--- a/contrib/bmake/unit-tests/cmdline-undefined.exp
+++ b/contrib/bmake/unit-tests/cmdline-undefined.exp
@@ -1,17 +1,17 @@
The = assignment operator
-make: "cmdline-undefined.mk" line 29: From the command line: Undefined is .
-make: "cmdline-undefined.mk" line 30: From .MAKEFLAGS '=': Undefined is .
-make: "cmdline-undefined.mk" line 31: From .MAKEFLAGS ':=': Undefined is .
-make: "cmdline-undefined.mk" line 35: From the command line: Undefined is now defined.
-make: "cmdline-undefined.mk" line 36: From .MAKEFLAGS '=': Undefined is now defined.
-make: "cmdline-undefined.mk" line 37: From .MAKEFLAGS ':=': Undefined is now defined.
+make: cmdline-undefined.mk:41: From the command line: Undefined is .
+make: cmdline-undefined.mk:42: From .MAKEFLAGS '=': Undefined is .
+make: cmdline-undefined.mk:43: From .MAKEFLAGS ':=': Undefined is .
+make: cmdline-undefined.mk:47: From the command line: Undefined is now defined.
+make: cmdline-undefined.mk:48: From .MAKEFLAGS '=': Undefined is now defined.
+make: cmdline-undefined.mk:49: From .MAKEFLAGS ':=': Undefined is now defined.
The := assignment operator
-make: "cmdline-undefined.mk" line 29: From the command line: Undefined is .
-make: "cmdline-undefined.mk" line 30: From .MAKEFLAGS '=': Undefined is .
-make: "cmdline-undefined.mk" line 31: From .MAKEFLAGS ':=': Undefined is .
-make: "cmdline-undefined.mk" line 35: From the command line: Undefined is now defined.
-make: "cmdline-undefined.mk" line 36: From .MAKEFLAGS '=': Undefined is now defined.
-make: "cmdline-undefined.mk" line 37: From .MAKEFLAGS ':=': Undefined is now defined.
+make: cmdline-undefined.mk:41: From the command line: Undefined is .
+make: cmdline-undefined.mk:42: From .MAKEFLAGS '=': Undefined is .
+make: cmdline-undefined.mk:43: From .MAKEFLAGS ':=': Undefined is .
+make: cmdline-undefined.mk:47: From the command line: Undefined is now defined.
+make: cmdline-undefined.mk:48: From .MAKEFLAGS '=': Undefined is now defined.
+make: cmdline-undefined.mk:49: From .MAKEFLAGS ':=': Undefined is now defined.
exit status 0
diff --git a/contrib/bmake/unit-tests/cmdline-undefined.mk b/contrib/bmake/unit-tests/cmdline-undefined.mk
index 5a3375cbbfb8..2deb944b1fab 100644
--- a/contrib/bmake/unit-tests/cmdline-undefined.mk
+++ b/contrib/bmake/unit-tests/cmdline-undefined.mk
@@ -1,6 +1,6 @@
-# $NetBSD: cmdline-undefined.mk,v 1.2 2020/11/04 04:49:33 rillig Exp $
+# $NetBSD: cmdline-undefined.mk,v 1.6 2025/06/30 21:44:39 rillig Exp $
#
-# Tests for undefined variable expressions in the command line.
+# Tests for undefined variables in expressions in the command line.
all:
# When the command line is parsed, variable assignments using the
@@ -8,6 +8,12 @@ all:
# (which probably occurs rarely in practice, if at all), but their
# variable value is not expanded, as usual.
#
+# expect+30: From the command line: Undefined is .
+# expect+30: From .MAKEFLAGS '=': Undefined is .
+# expect+30: From .MAKEFLAGS ':=': Undefined is .
+# expect+33: From the command line: Undefined is now defined.
+# expect+33: From .MAKEFLAGS '=': Undefined is now defined.
+# expect+33: From .MAKEFLAGS ':=': Undefined is now defined.
@echo 'The = assignment operator'
@${.MAKE} -f ${MAKEFILE} print-undefined \
CMDLINE='Undefined is $${UNDEFINED}.'
@@ -16,6 +22,12 @@ all:
# The interesting case is using the ':=' assignment operator, which
# expands its right-hand side. But only those variables that are
# defined.
+# expect+16: From the command line: Undefined is .
+# expect+16: From .MAKEFLAGS '=': Undefined is .
+# expect+16: From .MAKEFLAGS ':=': Undefined is .
+# expect+19: From the command line: Undefined is now defined.
+# expect+19: From .MAKEFLAGS '=': Undefined is now defined.
+# expect+19: From .MAKEFLAGS ':=': Undefined is now defined.
@echo 'The := assignment operator'
@${.MAKE} -f ${MAKEFILE} print-undefined \
CMDLINE:='Undefined is $${UNDEFINED}.'
diff --git a/contrib/bmake/unit-tests/cmdline.exp b/contrib/bmake/unit-tests/cmdline.exp
index e5748c5f88cb..063be7e83afe 100644
--- a/contrib/bmake/unit-tests/cmdline.exp
+++ b/contrib/bmake/unit-tests/cmdline.exp
@@ -1,5 +1,8 @@
makeobjdir-direct:
-show-objdir: <tmpdir>/6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5
+show-objdir: <tmpdir>/cmdline/6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5
makeobjdir-indirect:
-show-objdir: <tmpdir>/a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45/
+show-objdir: <tmpdir>/cmdline/a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45/
+space-and-comment:
+value # no comment $
+value # no comment $
exit status 0
diff --git a/contrib/bmake/unit-tests/cmdline.mk b/contrib/bmake/unit-tests/cmdline.mk
index f82e7f967ef8..c0c4ee287f2f 100644
--- a/contrib/bmake/unit-tests/cmdline.mk
+++ b/contrib/bmake/unit-tests/cmdline.mk
@@ -1,8 +1,8 @@
-# $NetBSD: cmdline.mk,v 1.3 2021/02/06 18:26:03 sjg Exp $
+# $NetBSD: cmdline.mk,v 1.7 2024/08/29 17:56:37 sjg Exp $
#
# Tests for command line parsing and related special variables.
-TMPBASE?= ${TMPDIR:U/tmp/uid${.MAKE.UID}}
+TMPBASE?= ${TMPDIR:U/tmp/uid${.MAKE.UID}}/cmdline
SUB1= a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45 # just a random UUID
SUB2= 6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5 # just a random UUID
MAKE_CMD= env TMPBASE=${TMPBASE}/${SUB1} ${.MAKE} -f ${MAKEFILE} -r
@@ -11,6 +11,8 @@ DIR12= ${TMPBASE}/${SUB1}/${SUB2}
all: prepare-dirs
all: makeobjdir-direct makeobjdir-indirect
+all: space-and-comment
+all: cleanup
prepare-dirs:
@rm -rf ${DIR2} ${DIR12}
@@ -23,7 +25,7 @@ makeobjdir-direct:
@${MAKE_CMD} MAKEOBJDIR=${DIR2} show-objdir
# The .OBJDIR can be set via the MAKEOBJDIR command line variable,
-# and that variable could even contain the usual modifiers.
+# and expressions based on that variable can contain the usual modifiers.
# Since the .OBJDIR=MAKEOBJDIR assignment happens very early,
# the SUB2 variable in the modifier is not defined yet and is therefore empty.
# The SUB1 in the resulting path comes from the environment variable TMPBASE,
@@ -34,3 +36,27 @@ makeobjdir-indirect:
show-objdir:
@echo $@: ${.OBJDIR:Q}
+
+
+# Variable assignments in the command line are handled differently from
+# variable assignments in makefiles. In the command line, trailing whitespace
+# is preserved, and the '#' does not start a comment. This is because the
+# low-level parsing from ParseRawLine does not take place.
+#
+# Preserving '#' and trailing whitespace has the benefit that when passing
+# such values to sub-makes via MAKEFLAGS, no special encoding is needed.
+# Leading whitespace in the variable value is discarded though, which makes
+# the behavior inconsistent.
+space-and-comment: .PHONY
+ @echo $@:
+
+ @env -i \
+ ${MAKE} -r -f /dev/null ' VAR= value # no comment ' -v VAR \
+ | sed 's,$$,$$,'
+
+ @env -i MAKEFLAGS="' VAR= value # no comment '" \
+ ${MAKE} -r -f /dev/null -v VAR \
+ | sed 's,$$,$$,'
+
+cleanup:
+ @rm -rf ${TMPBASE}
diff --git a/contrib/bmake/unit-tests/comment.mk b/contrib/bmake/unit-tests/comment.mk
index 2471c8cf659d..fea0f0b3d817 100644
--- a/contrib/bmake/unit-tests/comment.mk
+++ b/contrib/bmake/unit-tests/comment.mk
@@ -1,4 +1,4 @@
-# $NetBSD: comment.mk,v 1.4 2022/01/23 18:00:53 rillig Exp $
+# $NetBSD: comment.mk,v 1.7 2024/04/23 22:51:28 rillig Exp $
#
# Demonstrate how comments are written in makefiles.
@@ -23,7 +23,7 @@ on and on.
.endif # And after the closing directive.
VAR= # This comment makes the variable value empty.
- # ParseGetLine removes any whitespace before the
+ # ParseRawLine removes any whitespace before the
# comment.
.if ${VAR} != ""
. error
@@ -53,9 +53,9 @@ VAR= \# # Both in the assignment.
. error
.endif
-# Since 2012-03-24 the variable modifier :[#] does not need to be escaped.
-# To keep the parsing code simple, any "[#" does not start a comment, even
-# outside of a variable expression.
+# Since 2012-03-24 the modifier :[#] does not need to be escaped.
+# To keep the parsing code simple, the "#" in "[#" does not start a comment,
+# regardless of the syntactical context it appears in.
WORDS= ${VAR:[#]} [#
.if ${WORDS} != "1 [#"
. error
diff --git a/contrib/bmake/unit-tests/compat-error.exp b/contrib/bmake/unit-tests/compat-error.exp
index 256cb6d4361c..e06265e887d6 100644
--- a/contrib/bmake/unit-tests/compat-error.exp
+++ b/contrib/bmake/unit-tests/compat-error.exp
@@ -9,7 +9,7 @@ false 'fail2' '${.TARGET}' '$${.TARGET}'
: Making success3 out of nothing.
Stop.
-make: stopped in unit-tests
+make: stopped making "success1 fail1 success2 fail2 success3" in unit-tests
.ERROR target: <fail1>
.ERROR command: <>
exit status 1
diff --git a/contrib/bmake/unit-tests/compat-error.mk b/contrib/bmake/unit-tests/compat-error.mk
index 4cbc48d4b6bb..bcfeb14ac408 100644
--- a/contrib/bmake/unit-tests/compat-error.mk
+++ b/contrib/bmake/unit-tests/compat-error.mk
@@ -1,17 +1,24 @@
-# $NetBSD: compat-error.mk,v 1.3 2020/12/13 19:33:53 rillig Exp $
+# $NetBSD: compat-error.mk,v 1.5 2022/05/08 06:51:27 rillig Exp $
#
# Test detailed error handling in compat mode.
#
-# Until 2020-12-13, .ERROR_TARGET was success3, which was wrong.
-# Since compat.c 1.215 from 2020-12-13, it is 'fail1', which is the first
-# failed top-level target. XXX: Even better would be if .ERROR_TARGET were
-# the smallest target that caused the build to fail, even if it were a
-# sub-sub-sub-dependency of a top-level target.
+# Make several targets that alternately succeed and fail.
#
-# XXX: As of 2020-12-13, .ERROR_CMD is empty, which is wrong.
+# The first failing top-level target is recorded in '.ERROR_TARGET'. While
+# this information may give a hint as to which target failed, it would be more
+# useful at that point to know the actual target that failed, or the complete
+# chain from root cause to top-level target.
+#
+# Historic bugs
+# Before compat.c 1.215 from 2020-12-13, '.ERROR_TARGET' was 'success3',
+# which was obviously wrong.
+#
+# Bugs
+# As of 2020-12-13, '.ERROR_CMD' is empty, which does not provide any
+# insight into the command that actually failed.
#
# See also:
-# Compat_Run
+# Compat_MakeAll
#
# The commit that added the NULL command to gn->commands:
# CVS: 1994.06.06.22.45.??
@@ -20,10 +27,10 @@
# 2020: LstNode_SetNull(cmdNode);
#
# The commit that skipped NULL commands for .ERROR_CMD:
-# CVS: 2016.08.11.19.53.??
+# CVS: 2016.08.11.19.53.17
# Git: 58b23478b7353d46457089e726b07a49197388e4
-.MAKEFLAGS: success1 fail1 success2 fail2 success3
+.MAKEFLAGS: -k success1 fail1 success2 fail2 success3
success1 success2 success3:
: Making ${.TARGET} out of nothing.
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp b/contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp
index 72d3d935755b..81c73d887c18 100644
--- a/contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp
@@ -1,5 +1,5 @@
-make: "cond-cmp-numeric-eq.mk" line 67: Malformed conditional (!(12345 = 12345))
-make: "cond-cmp-numeric-eq.mk" line 74: Malformed conditional (!(12345 === 12345))
+make: cond-cmp-numeric-eq.mk:68: Malformed conditional "!(12345 = 12345)"
+make: cond-cmp-numeric-eq.mk:76: Malformed conditional "!(12345 === 12345)"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-eq.mk b/contrib/bmake/unit-tests/cond-cmp-numeric-eq.mk
index c6b39876e75e..ea60f6f7b18d 100755
--- a/contrib/bmake/unit-tests/cond-cmp-numeric-eq.mk
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-eq.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-cmp-numeric-eq.mk,v 1.5 2020/11/08 21:47:59 rillig Exp $
+# $NetBSD: cond-cmp-numeric-eq.mk,v 1.9 2025/06/28 22:39:28 rillig Exp $
#
# Tests for numeric comparisons with the == operator in .if conditions.
@@ -40,7 +40,7 @@
. error
.endif
-# As of 2020-08-23, numeric comparison is implemented as parsing both sides
+# Numeric comparison works by parsing both sides
# as double, and then performing a normal comparison. The range of double is
# typically 16 or 17 significant digits, therefore these two numbers seem to
# be equal.
@@ -64,6 +64,7 @@
.endif
# There is no = operator for numbers.
+# expect+1: Malformed conditional "!(12345 = 12345)"
.if !(12345 = 12345)
. error
.else
@@ -71,11 +72,9 @@
.endif
# There is no === operator for numbers either.
+# expect+1: Malformed conditional "!(12345 === 12345)"
.if !(12345 === 12345)
. error
.else
. error
.endif
-
-all:
- @:;
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-ge.mk b/contrib/bmake/unit-tests/cond-cmp-numeric-ge.mk
index e64be7f0c1a8..62975f950c2a 100755
--- a/contrib/bmake/unit-tests/cond-cmp-numeric-ge.mk
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-ge.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-cmp-numeric-ge.mk,v 1.2 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-cmp-numeric-ge.mk,v 1.3 2023/09/07 05:36:33 rillig Exp $
#
# Tests for numeric comparisons with the >= operator in .if conditions.
@@ -62,7 +62,7 @@
. error
.endif
-# As of 2020-08-23, numeric comparison is implemented as parsing both sides
+# Numeric comparison works by parsing both sides
# as double, and then performing a normal comparison. The range of double is
# typically 16 or 17 significant digits, therefore these two numbers seem to
# be equal.
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-gt.mk b/contrib/bmake/unit-tests/cond-cmp-numeric-gt.mk
index 1cdcc9891d6f..6134f7daf3fe 100755
--- a/contrib/bmake/unit-tests/cond-cmp-numeric-gt.mk
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-gt.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-cmp-numeric-gt.mk,v 1.2 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-cmp-numeric-gt.mk,v 1.3 2023/09/07 05:36:33 rillig Exp $
#
# Tests for numeric comparisons with the > operator in .if conditions.
@@ -61,11 +61,11 @@
. error
.endif
-# As of 2020-08-23, numeric comparison is implemented as parsing both sides
+# Numeric comparison works by parsing both sides
# as double, and then performing a normal comparison. The range of double is
# typically 16 or 17 significant digits, therefore these two numbers seem to
# be equal.
-.if 1.000000000000000001 > 1.000000000000000002
+.if 1.000000000000000002 > 1.000000000000000001
. error
.endif
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-le.mk b/contrib/bmake/unit-tests/cond-cmp-numeric-le.mk
index 05f5e8dba312..231db76ba618 100755
--- a/contrib/bmake/unit-tests/cond-cmp-numeric-le.mk
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-le.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-cmp-numeric-le.mk,v 1.2 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-cmp-numeric-le.mk,v 1.3 2023/09/07 05:36:33 rillig Exp $
#
# Tests for numeric comparisons with the <= operator in .if conditions.
@@ -62,7 +62,7 @@
. error
.endif
-# As of 2020-08-23, numeric comparison is implemented as parsing both sides
+# Numeric comparison works by parsing both sides
# as double, and then performing a normal comparison. The range of double is
# typically 16 or 17 significant digits, therefore these two numbers seem to
# be equal.
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-lt.mk b/contrib/bmake/unit-tests/cond-cmp-numeric-lt.mk
index b0dddd591543..9529fa9ff0eb 100755
--- a/contrib/bmake/unit-tests/cond-cmp-numeric-lt.mk
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-lt.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-cmp-numeric-lt.mk,v 1.2 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-cmp-numeric-lt.mk,v 1.3 2023/09/07 05:36:33 rillig Exp $
#
# Tests for numeric comparisons with the < operator in .if conditions.
@@ -61,7 +61,7 @@
. error
.endif
-# As of 2020-08-23, numeric comparison is implemented as parsing both sides
+# Numeric comparison works by parsing both sides
# as double, and then performing a normal comparison. The range of double is
# typically 16 or 17 significant digits, therefore these two numbers seem to
# be equal.
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-ne.mk b/contrib/bmake/unit-tests/cond-cmp-numeric-ne.mk
index 0a366a905a21..e311d21f0a54 100755
--- a/contrib/bmake/unit-tests/cond-cmp-numeric-ne.mk
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric-ne.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-cmp-numeric-ne.mk,v 1.2 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-cmp-numeric-ne.mk,v 1.3 2023/09/07 05:36:33 rillig Exp $
#
# Tests for numeric comparisons with the != operator in .if conditions.
@@ -37,7 +37,7 @@
. error
.endif
-# As of 2020-08-23, numeric comparison is implemented as parsing both sides
+# Numeric comparison works by parsing both sides
# as double, and then performing a normal comparison. The range of double is
# typically 16 or 17 significant digits, therefore these two numbers seem to
# be equal.
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric.exp b/contrib/bmake/unit-tests/cond-cmp-numeric.exp
index d913a44ff889..2959ad99716c 100644
--- a/contrib/bmake/unit-tests/cond-cmp-numeric.exp
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric.exp
@@ -1,15 +1,15 @@
CondParser_Eval: !(${:UINF} > 1e100)
-make: "cond-cmp-numeric.mk" line 11: String comparison operator must be either == or !=
+make: cond-cmp-numeric.mk:15: Comparison with ">" requires both operands "INF" and "1e100" to be numeric
CondParser_Eval: ${:UNaN} > NaN
-make: "cond-cmp-numeric.mk" line 16: String comparison operator must be either == or !=
+make: cond-cmp-numeric.mk:21: Comparison with ">" requires both operands "NaN" and "NaN" to be numeric
CondParser_Eval: !(${:UNaN} == NaN)
Comparing "NaN" == "NaN"
CondParser_Eval: 123 ! 123
-make: "cond-cmp-numeric.mk" line 34: Malformed conditional (123 ! 123)
+make: cond-cmp-numeric.mk:38: Malformed conditional "123 ! 123"
CondParser_Eval: ${:U 123} < 124
Comparing 123.000000 < 124.000000
CondParser_Eval: ${:U123 } < 124
-make: "cond-cmp-numeric.mk" line 50: String comparison operator must be either == or !=
+make: cond-cmp-numeric.mk:54: Comparison with "<" requires both operands "123 " and "124" to be numeric
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric.mk b/contrib/bmake/unit-tests/cond-cmp-numeric.mk
index b34e5bfc0a06..abe5cc4cce9c 100644
--- a/contrib/bmake/unit-tests/cond-cmp-numeric.mk
+++ b/contrib/bmake/unit-tests/cond-cmp-numeric.mk
@@ -1,6 +1,9 @@
-# $NetBSD: cond-cmp-numeric.mk,v 1.5 2021/07/29 06:31:18 rillig Exp $
+# $NetBSD: cond-cmp-numeric.mk,v 1.9 2025/06/28 22:39:28 rillig Exp $
#
# Tests for numeric comparisons in .if conditions.
+#
+# See also:
+# cond-token-number.mk
.MAKEFLAGS: -dc
@@ -8,11 +11,13 @@
# Even if strtod(3) parses "INF" as +Infinity, make does not accept this
# since it is not really a number; see TryParseNumber.
+# expect+1: Comparison with ">" requires both operands "INF" and "1e100" to be numeric
.if !(${:UINF} > 1e100)
. error
.endif
# Neither is NaN a number; see TryParseNumber.
+# expect+1: Comparison with ">" requires both operands "NaN" and "NaN" to be numeric
.if ${:UNaN} > NaN
. error
.endif
@@ -29,8 +34,7 @@
# whether the operator is valid, leaving the rest of the work to the
# evaluation functions EvalCompareNum and EvalCompareStr. Ensure that this
# parse error is properly reported.
-#
-# XXX: The warning message does not mention the actual operator.
+# expect+1: Malformed conditional "123 ! 123"
.if 123 ! 123
. error
.else
@@ -46,7 +50,7 @@
# Trailing spaces are NOT allowed for numbers.
# See EvalCompare and TryParseNumber.
-# expect+1: String comparison operator must be either == or !=
+# expect+1: Comparison with "<" requires both operands "123 " and "124" to be numeric
.if ${:U123 } < 124
. error
.else
@@ -54,4 +58,3 @@
.endif
all:
- @:;
diff --git a/contrib/bmake/unit-tests/cond-cmp-string.exp b/contrib/bmake/unit-tests/cond-cmp-string.exp
index a10341ed2121..8291d5300c3b 100644
--- a/contrib/bmake/unit-tests/cond-cmp-string.exp
+++ b/contrib/bmake/unit-tests/cond-cmp-string.exp
@@ -1,11 +1,11 @@
-make: "cond-cmp-string.mk" line 18: Malformed conditional (str != str)
-make: "cond-cmp-string.mk" line 42: Malformed conditional ("string" != "str""ing")
-make: "cond-cmp-string.mk" line 49: Malformed conditional (!("value" = "value"))
-make: "cond-cmp-string.mk" line 56: Malformed conditional (!("value" === "value"))
-make: "cond-cmp-string.mk" line 113: String comparison operator must be either == or !=
-make: "cond-cmp-string.mk" line 120: String comparison operator must be either == or !=
-make: "cond-cmp-string.mk" line 127: String comparison operator must be either == or !=
-make: "cond-cmp-string.mk" line 134: String comparison operator must be either == or !=
+make: cond-cmp-string.mk:19: Malformed conditional "str != str"
+make: cond-cmp-string.mk:44: Malformed conditional ""string" != "str""ing""
+make: cond-cmp-string.mk:52: Malformed conditional "!("value" = "value")"
+make: cond-cmp-string.mk:60: Malformed conditional "!("value" === "value")"
+make: cond-cmp-string.mk:118: Comparison with "<" requires both operands "string" and "string" to be numeric
+make: cond-cmp-string.mk:126: Comparison with "<=" requires both operands "string" and "string" to be numeric
+make: cond-cmp-string.mk:134: Comparison with ">" requires both operands "string" and "string" to be numeric
+make: cond-cmp-string.mk:142: Comparison with ">=" requires both operands "string" and "string" to be numeric
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-cmp-string.mk b/contrib/bmake/unit-tests/cond-cmp-string.mk
index 305a41099b98..56427bda11d0 100644
--- a/contrib/bmake/unit-tests/cond-cmp-string.mk
+++ b/contrib/bmake/unit-tests/cond-cmp-string.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-cmp-string.mk,v 1.15 2021/12/11 09:53:53 rillig Exp $
+# $NetBSD: cond-cmp-string.mk,v 1.21 2025/06/28 22:39:28 rillig Exp $
#
# Tests for string comparisons in .if conditions.
@@ -15,16 +15,17 @@
# The left-hand side of the comparison must be enclosed in quotes.
# This one is not enclosed in quotes and thus generates an error message.
+# expect+1: Malformed conditional "str != str"
.if str != str
. error
.endif
-# The left-hand side of the comparison requires that any variable expression
-# is defined.
+# An expression that occurs on the left-hand side of the comparison must be
+# defined.
#
# The variable named "" is never defined, nevertheless it can be used as a
-# starting point for variable expressions. Applying the :U modifier to such
-# an undefined expression turns it into a defined expression.
+# starting point for an expression. Applying the :U modifier to such an
+# undefined expression turns it into a defined expression.
#
# See ApplyModifier_Defined and DEF_DEFINED.
.if ${:Ustr} != "str"
@@ -39,6 +40,7 @@
# It is not possible to concatenate two string literals to form a single
# string. In C, Python and the shell this is possible, but not in make.
+# expect+1: Malformed conditional ""string" != "str""ing""
.if "string" != "str""ing"
. error
.else
@@ -46,6 +48,7 @@
.endif
# There is no = operator for strings.
+# expect+1: Malformed conditional "!("value" = "value")"
.if !("value" = "value")
. error
.else
@@ -53,20 +56,22 @@
.endif
# There is no === operator for strings either.
+# expect+1: Malformed conditional "!("value" === "value")"
.if !("value" === "value")
. error
.else
. error
.endif
-# A variable expression can be enclosed in double quotes.
+# An expression can be enclosed in double quotes.
.if ${:Uword} != "${:Uword}"
. error
.endif
# Between 2003-01-01 (maybe even earlier) and 2020-10-30, adding one of the
-# characters " \t!=><" directly after a variable expression resulted in a
-# "Malformed conditional", even though the string was well-formed.
+# characters " \t!=><" directly after an expression in a string literal
+# resulted in a "Malformed conditional", even though the string was
+# well-formed.
.if ${:Uword } != "${:Uword} "
. error
.endif
@@ -85,13 +90,12 @@
. error
.endif
-# Adding another variable expression to the string literal works though.
+# Adding another expression to the string literal works though.
.if ${:Uword} != "${:Uwo}${:Urd}"
. error
.endif
-# Adding a space at the beginning of the quoted variable expression works
-# though.
+# Adding a space at the beginning of the quoted expression works though.
.if ${:U word } != " ${:Uword} "
. error
.endif
@@ -110,6 +114,7 @@
.endif
# Strings cannot be compared relationally, only for equality.
+# expect+1: Comparison with "<" requires both operands "string" and "string" to be numeric
.if "string" < "string"
. error
.else
@@ -117,6 +122,7 @@
.endif
# Strings cannot be compared relationally, only for equality.
+# expect+1: Comparison with "<=" requires both operands "string" and "string" to be numeric
.if "string" <= "string"
. error
.else
@@ -124,6 +130,7 @@
.endif
# Strings cannot be compared relationally, only for equality.
+# expect+1: Comparison with ">" requires both operands "string" and "string" to be numeric
.if "string" > "string"
. error
.else
@@ -131,8 +138,17 @@
.endif
# Strings cannot be compared relationally, only for equality.
+# expect+1: Comparison with ">=" requires both operands "string" and "string" to be numeric
.if "string" >= "string"
. error
.else
. error
.endif
+
+# Two expressions with different values compare unequal.
+VAR1= value1
+VAR2= value2
+.if ${VAR1} != ${VAR2}
+.else
+. error
+.endif
diff --git a/contrib/bmake/unit-tests/cond-cmp-unary.exp b/contrib/bmake/unit-tests/cond-cmp-unary.exp
index 89f90dc1651f..5a1bead87d0a 100755
--- a/contrib/bmake/unit-tests/cond-cmp-unary.exp
+++ b/contrib/bmake/unit-tests/cond-cmp-unary.exp
@@ -1,2 +1,2 @@
-make: "cond-cmp-unary.mk" line 53: This is only reached because of a bug in EvalNotEmpty.
+make: cond-cmp-unary.mk:54: This is only reached because of a bug in EvalTruthy.
exit status 0
diff --git a/contrib/bmake/unit-tests/cond-cmp-unary.mk b/contrib/bmake/unit-tests/cond-cmp-unary.mk
index 168de0f30e3f..80626a279358 100755
--- a/contrib/bmake/unit-tests/cond-cmp-unary.mk
+++ b/contrib/bmake/unit-tests/cond-cmp-unary.mk
@@ -1,11 +1,11 @@
-# $NetBSD: cond-cmp-unary.mk,v 1.2 2020/11/11 07:30:11 rillig Exp $
+# $NetBSD: cond-cmp-unary.mk,v 1.6 2023/11/19 21:47:52 rillig Exp $
#
# Tests for unary comparisons in .if conditions, that is, comparisons with
# a single operand. If the operand is a number, it is compared to zero,
# if it is a string, it is tested for emptiness.
-# The number 0 evaluates to false.
-.if 0
+# The number 0 in all its various representations evaluates to false.
+.if 0 || 0.0 || 0e0 || 0.0e0 || 0.0e10
. error
.endif
@@ -24,35 +24,52 @@
. error
.endif
-# The empty string may come from a variable expression.
+# The empty string may come from an expression.
#
-# XXX: As of 2020-11-11, this empty string is interpreted "as a number" in
-# EvalNotEmpty, which is plain wrong. The bug is in TryParseNumber.
+# XXX: As of 2023-06-01, this empty string is interpreted "as a number" in
+# EvalTruthy, which is plain wrong. The bug is in TryParseNumber.
.if ${:U}
. error
.endif
-# A variable expression that is not surrounded by quotes is interpreted
+# An expression that is not surrounded by quotes is interpreted
# as a number if possible, otherwise as a string.
.if ${:U0}
. error
.endif
-# A non-zero number from a variable expression evaluates to true.
+# A non-zero number from an expression evaluates to true.
.if !${:U12345}
. error
.endif
# A string of whitespace should evaluate to false.
#
-# XXX: As of 2020-11-11, the implementation in EvalNotEmpty does not skip
+# XXX: As of 2023-06-01, the implementation in EvalTruthy does not skip
# whitespace before testing for the end. This was probably an oversight in
# a commit from 1992-04-15 saying "A variable is empty when it just contains
# spaces".
.if ${:U }
-. info This is only reached because of a bug in EvalNotEmpty.
+# expect+1: This is only reached because of a bug in EvalTruthy.
+. info This is only reached because of a bug in EvalTruthy.
.else
. error
.endif
+# The condition '${VAR:M*}' is almost equivalent to '${VAR:M*} != ""'. The
+# only case where they differ is for a single word whose numeric value is zero.
+.if ${:U0:M*}
+. error
+.endif
+.if ${:U0:M*} == ""
+. error
+.endif
+# Multiple words cannot be parsed as a single number, thus evaluating to true.
+.if !${:U0 0:M*}
+. error
+.endif
+.if ${:U0 0:M*} == ""
+. error
+.endif
+
all: # nothing
diff --git a/contrib/bmake/unit-tests/cond-eof.exp b/contrib/bmake/unit-tests/cond-eof.exp
index 3016a9b27805..fb23e248178e 100644
--- a/contrib/bmake/unit-tests/cond-eof.exp
+++ b/contrib/bmake/unit-tests/cond-eof.exp
@@ -1,6 +1,6 @@
-make: "cond-eof.mk" line 15: Malformed conditional (0 ${SIDE_EFFECT} ${SIDE_EFFECT2})
-make: "cond-eof.mk" line 17: Malformed conditional (1 ${SIDE_EFFECT} ${SIDE_EFFECT2})
-make: "cond-eof.mk" line 19: Malformed conditional ((0) ${SIDE_EFFECT} ${SIDE_EFFECT2})
+make: cond-eof.mk:17: Malformed conditional "0 ${SIDE_EFFECT} ${SIDE_EFFECT2}"
+make: cond-eof.mk:20: Malformed conditional "1 ${SIDE_EFFECT} ${SIDE_EFFECT2}"
+make: cond-eof.mk:23: Malformed conditional "(0) ${SIDE_EFFECT} ${SIDE_EFFECT2}"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-eof.mk b/contrib/bmake/unit-tests/cond-eof.mk
index ddf4a4cd20c8..04d79d0783ad 100644
--- a/contrib/bmake/unit-tests/cond-eof.mk
+++ b/contrib/bmake/unit-tests/cond-eof.mk
@@ -1,20 +1,24 @@
-# $NetBSD: cond-eof.mk,v 1.3 2021/12/10 23:12:44 rillig Exp $
+# $NetBSD: cond-eof.mk,v 1.8 2025/06/28 22:39:28 rillig Exp $
#
-# Tests for parsing conditions, especially the end of such conditions, which
-# are represented as the token TOK_EOF.
+# Tests for parsing the end of '.if' conditions, which are represented as the
+# token TOK_EOF.
+
SIDE_EFFECT= ${:!echo 'side effect' 1>&2!}
SIDE_EFFECT2= ${:!echo 'side effect 2' 1>&2!}
# In the following conditions, ${SIDE_EFFECT} is the position of the first
# parse error. Before cond.c 1.286 from 2021-12-10, it was always fully
-# evaluated, even if it was not necessary to expand the variable expression.
+# evaluated, even if it was not necessary to expand the expression.
# These syntax errors are an edge case that does not occur during normal
# operation. Still, it is easy to avoid evaluating these expressions, just in
# case they have side effects.
+# expect+1: Malformed conditional "0 ${SIDE_EFFECT} ${SIDE_EFFECT2}"
.if 0 ${SIDE_EFFECT} ${SIDE_EFFECT2}
.endif
+# expect+1: Malformed conditional "1 ${SIDE_EFFECT} ${SIDE_EFFECT2}"
.if 1 ${SIDE_EFFECT} ${SIDE_EFFECT2}
.endif
+# expect+1: Malformed conditional "(0) ${SIDE_EFFECT} ${SIDE_EFFECT2}"
.if (0) ${SIDE_EFFECT} ${SIDE_EFFECT2}
.endif
diff --git a/contrib/bmake/unit-tests/cond-func-commands.mk b/contrib/bmake/unit-tests/cond-func-commands.mk
index e127a8ebdc03..d193587cd2c1 100644
--- a/contrib/bmake/unit-tests/cond-func-commands.mk
+++ b/contrib/bmake/unit-tests/cond-func-commands.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-func-commands.mk,v 1.5 2020/11/15 14:07:53 rillig Exp $
+# $NetBSD: cond-func-commands.mk,v 1.6 2025/01/10 23:00:38 rillig Exp $
#
# Tests for the commands() function in .if conditions.
@@ -33,5 +33,10 @@ target:
. error
.endif
+# Expressions in the argument of a function call don't have to be defined.
+.if commands(${UNDEF})
+. error
+.endif
+
all:
@:;
diff --git a/contrib/bmake/unit-tests/cond-func-defined.exp b/contrib/bmake/unit-tests/cond-func-defined.exp
index 878f56de2ecc..04b57061f803 100644
--- a/contrib/bmake/unit-tests/cond-func-defined.exp
+++ b/contrib/bmake/unit-tests/cond-func-defined.exp
@@ -1,8 +1,5 @@
-make: "cond-func-defined.mk" line 23: Missing closing parenthesis for defined()
-make: "cond-func-defined.mk" line 33: Missing closing parenthesis for defined()
-make: "cond-func-defined.mk" line 45: In .for loops, variable expressions for the loop variables are
-make: "cond-func-defined.mk" line 46: substituted at evaluation time. There is no actual variable
-make: "cond-func-defined.mk" line 47: involved, even if it feels like it.
+make: cond-func-defined.mk:24: Missing ")" after argument "A" for "defined"
+make: cond-func-defined.mk:34: Missing ")" after argument "DEF" for "defined"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-func-defined.mk b/contrib/bmake/unit-tests/cond-func-defined.mk
index 43db548a572b..52eeaec8ccfc 100644
--- a/contrib/bmake/unit-tests/cond-func-defined.mk
+++ b/contrib/bmake/unit-tests/cond-func-defined.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-func-defined.mk,v 1.8 2021/12/12 08:55:28 rillig Exp $
+# $NetBSD: cond-func-defined.mk,v 1.15 2025/06/28 22:39:28 rillig Exp $
#
# Tests for the defined() function in .if conditions.
@@ -20,16 +20,17 @@ ${:UA B}= variable name with spaces
.endif
# The argument of a function must not directly contain whitespace.
+# expect+1: Missing ")" after argument "A" for "defined"
.if !defined(A B)
. error
.endif
-# If necessary, the whitespace can be generated by a variable expression.
+# If necessary, the whitespace can be generated by an expression.
.if !defined(${:UA B})
. error
.endif
-# Parse error: missing closing parenthesis; see ParseWord.
+# expect+1: Missing ")" after argument "DEF" for "defined"
.if defined(DEF
. error
.else
@@ -42,11 +43,21 @@ ${:UA B}= variable name with spaces
. if defined(var)
. error
. else
-. info In .for loops, variable expressions for the loop variables are
-. info substituted at evaluation time. There is no actual variable
-. info involved, even if it feels like it.
+# In .for loops, expressions based on the loop variables are substituted at
+# evaluation time. There is no actual variable involved, even if the code in
+# the makefiles looks like it.
. endif
.endfor
-all:
- @:;
+# Expressions in the argument of a function call don't have to be defined.
+.if defined(${UNDEF})
+. error
+.endif
+
+# Neither of the conditions is true. Before July 2020, the right-hand
+# condition was evaluated even though it was irrelevant.
+.if defined(UNDEF) && ${UNDEF:Mx} != ""
+. error
+.endif
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/cond-func-empty.exp b/contrib/bmake/unit-tests/cond-func-empty.exp
index d1dfda7c03ee..7861edc74ba2 100644
--- a/contrib/bmake/unit-tests/cond-func-empty.exp
+++ b/contrib/bmake/unit-tests/cond-func-empty.exp
@@ -1,5 +1,5 @@
-make: "cond-func-empty.mk" line 149: Unclosed variable "WORD"
-make: "cond-func-empty.mk" line 149: Malformed conditional (empty(WORD)
+make: cond-func-empty.mk:156: warning: Invalid character " " in variable name " WORD "
+make: cond-func-empty.mk:168: Unclosed variable "WORD"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-func-empty.mk b/contrib/bmake/unit-tests/cond-func-empty.mk
index 24cb7a680b2a..ab097b026dcb 100644
--- a/contrib/bmake/unit-tests/cond-func-empty.mk
+++ b/contrib/bmake/unit-tests/cond-func-empty.mk
@@ -1,18 +1,19 @@
-# $NetBSD: cond-func-empty.mk,v 1.17 2021/12/28 22:13:56 rillig Exp $
+# $NetBSD: cond-func-empty.mk,v 1.29 2025/06/11 18:49:58 sjg Exp $
#
-# Tests for the empty() function in .if conditions, which tests a variable
+# Tests for the empty() function in .if conditions, which tests an
# expression for emptiness.
#
-# Note that the argument in the parentheses is a variable name, not a variable
-# expression, optionally followed by variable modifiers.
+# Note that the argument in the parentheses is a variable name, not an
+# expression. That name may be followed by ':...' modifiers.
#
.undef UNDEF
EMPTY= # empty
SPACE= ${:U }
+ZERO= 0
WORD= word
-# An undefined variable is empty.
+# An undefined variable counts as empty.
.if !empty(UNDEF)
. error
.endif
@@ -24,11 +25,13 @@ WORD= word
. error
.endif
-# The :S modifier replaces the empty value with an actual word. The
-# expression is now no longer empty, but it is still based on an undefined
-# variable (DEF_UNDEF). There are a few variable modifiers that turn an
-# undefined expression into a defined expression, among them :U and :D, but
-# not :S.
+# The :S modifier replaces the empty value with an actual word. After
+# applying the :S modifier to the expression, its value is 'empty', so it is
+# no longer empty, but it is still based on an undefined variable. There are
+# a few modifiers that turn an undefined expression into a defined expression,
+# among them :U and :D, but not :S. Therefore, at the end of evaluating the
+# expression, the expression is still undefined, so its final value becomes an
+# empty string.
#
# XXX: This is hard to explain to someone who doesn't know these
# implementation details.
@@ -45,15 +48,17 @@ WORD= word
. error
.endif
-# And now to the surprising part. Applying the following :S modifier to the
-# undefined expression makes it non-empty, but the expression is still in
-# state DEF_UNDEF. The :U modifier that follows only looks at the state
-# DEF_UNDEF to decide whether the variable is defined or not. This kind of
-# makes sense since the :U modifier tests the _variable_, not the
+# When an expression is based on an undefined variable, its modifiers interact
+# in sometimes surprising ways. Applying the :S modifier to the undefined
+# expression makes its value non-empty, but doesn't change that the expression
+# is based on an undefined variable. The :U modifier that follows only looks
+# at the definedness state to decide whether the variable is defined or not.
+# This kind of makes sense since the :U modifier tests the _variable_, not the
# _expression_.
#
-# But since the variable was undefined to begin with, the fallback value from
-# the :U modifier is used in this expression.
+# Since the variable was undefined to begin with, the fallback value from the
+# :U modifier is used in this expression, instead of keeping the 'value' from
+# the :S modifier.
#
.if ${UNDEF:S,^$,value,W:Ufallback} != "fallback"
. error
@@ -74,6 +79,17 @@ WORD= word
. error
.endif
+# The variable ZERO has the numeric value 0, but is not empty. This is a
+# subtle difference between using either 'empty(ZERO)' or the expression
+# '${ZERO}' in a condition.
+.if empty(ZERO)
+. error
+.elif ${ZERO}
+. error
+.elif ${ZERO} == ""
+. error
+.endif
+
# The following example constructs an expression with the variable name ""
# and the value " ". This expression counts as empty since the value contains
# only whitespace.
@@ -88,21 +104,23 @@ WORD= word
# Now the variable named " " gets a non-empty value, which demonstrates that
# neither leading nor trailing spaces are trimmed in the argument of the
-# function. If the spaces were trimmed, the variable name would be "" and
-# that variable is indeed undefined. Since CondParser_FuncCallEmpty calls
-# Var_Parse without VARE_UNDEFERR, the value of the undefined variable is
-# returned as an empty string.
+# function. If the spaces were trimmed, the variable name would be "", and
+# that variable is indeed undefined. Since CondParser_FuncCallEmpty allows
+# subexpressions to be based on undefined variables, the value of the
+# undefined variable "" would be returned as an empty string.
${:U }= space
.if empty( )
. error
.endif
-# The value of the following expression is " word", which is not empty.
+# The value of the following expression is " word", which is not empty. To be
+# empty, _all_ characters in the expression value have to be whitespace, not
+# only the first.
.if empty(:U word)
. error
.endif
-# The :L modifier creates a variable expression that has the same value as
+# The :L modifier creates an expression that has the same value as
# its name, which both are "VAR" in this case. The value is therefore not
# empty.
.if empty(VAR:L)
@@ -120,20 +138,21 @@ ${:U }= space
. error
.endif
-# Ensure that variable expressions that appear as part of the argument are
-# properly parsed. Typical use cases for this are .for loops, which are
-# expanded to exactly these ${:U} expressions.
+# Ensure that expressions that appear as part of the function call
+# argument are properly parsed. Typical use cases for this are .for loops,
+# which are expanded to exactly these ${:U} expressions.
#
-# If everything goes well, the argument expands to "WORD", and that variable
-# is defined at the beginning of this file. The surrounding 'W' and 'D'
-# ensure that CondParser_FuncCallEmpty keeps track of the parsing position,
-# both before and after the call to Var_Parse.
+# The argument expands to "WORD", and that variable is defined at the
+# beginning of this file. The surrounding 'W' and 'D' ensure that
+# CondParser_FuncCallEmpty keeps track of the parsing position, both before
+# and after the call to Var_Parse.
.if empty(W${:UOR}D)
. error
.endif
-# There may be spaces at the outside of the parentheses.
+# There may be spaces outside the parentheses.
# Spaces inside the parentheses are interpreted as part of the variable name.
+# expect+1: warning: Invalid character " " in variable name " WORD "
.if ! empty ( WORD )
. error
.endif
@@ -145,7 +164,7 @@ ${:U WORD }= variable name with spaces
. error
.endif
-# Parse error: missing closing parenthesis.
+# expect+1: Unclosed variable "WORD"
.if empty(WORD
. error
.else
@@ -159,10 +178,9 @@ ${:U WORD }= variable name with spaces
# Since at least 1993, the manual page claimed that irrelevant parts of
# conditions were not evaluated, but that was wrong for a long time. The
# expressions in irrelevant parts of the condition were actually evaluated,
-# they just allowed undefined variables to be used in the conditions, and the
-# result of evaluating them was not used further. These unnecessary
-# evaluations were fixed in several commits, starting with var.c 1.226 from
-# 2020-07-02.
+# they just allowed undefined variables to be used in the conditions. These
+# unnecessary evaluations were fixed in several commits, starting with var.c
+# 1.226 from 2020-07-02.
#
# In this example, the variable "VARNAME2" is not defined, so evaluation of
# the condition should have stopped at this point, and the rest of the
@@ -170,25 +188,29 @@ ${:U WORD }= variable name with spaces
# side containing the '!empty' was evaluated though, as it had always been.
#
# When evaluating the !empty condition, the variable name was parsed as
-# "VARNAME${:U2}", but without expanding any nested variable expression, in
+# "VARNAME${:U2}", but without expanding any nested expression, in
# this case the ${:U2}. The expression '${:U2}' was replaced with an empty
# string, the resulting variable name was thus "VARNAME". This conceptually
# wrong variable name should have been discarded quickly after parsing it, to
# prevent it from doing any harm.
#
-# The variable expression was expanded though, and this was wrong. The
-# expansion was done without VARE_WANTRES (called VARF_WANTRES back
-# then) though. This had the effect that the ${:U1} from the value of VARNAME
-# expanded to an empty string. This in turn created the seemingly recursive
-# definition VARNAME=${VARNAME}, and that definition was never meant to be
-# expanded.
+# The expression was evaluated, and this was wrong. The evaluation was done
+# without VARE_EVAL (called VARF_WANTRES back then) though. This had the
+# effect that the ${:U1} from the value of VARNAME evaluated to an empty
+# string. This in turn created the seemingly recursive definition
+# VARNAME=${VARNAME}, and that definition was evaluated even though it was
+# never meant to be evaluated.
#
-# This was fixed by expanding nested variable expressions in the variable name
-# only if the flag VARE_WANTRES is given.
+# This was fixed by evaluating nested expressions in the variable name only
+# when the whole expression was evaluated as well.
VARNAME= ${VARNAME${:U1}}
.if defined(VARNAME${:U2}) && !empty(VARNAME${:U2})
.endif
+# Expressions in the argument of a function call don't have to be defined.
+.if !empty(${UNDEF})
+. error
+.endif
# 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
diff --git a/contrib/bmake/unit-tests/cond-func-exists.mk b/contrib/bmake/unit-tests/cond-func-exists.mk
index 48d7e727dc3f..c68507ca1ab9 100644
--- a/contrib/bmake/unit-tests/cond-func-exists.mk
+++ b/contrib/bmake/unit-tests/cond-func-exists.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-func-exists.mk,v 1.6 2020/11/30 20:12:29 rillig Exp $
+# $NetBSD: cond-func-exists.mk,v 1.8 2025/01/10 23:00:38 rillig Exp $
#
# Tests for the exists() function in .if conditions.
@@ -17,7 +17,7 @@
.endif
# The only way to escape characters that would otherwise influence the parser
-# is to enclose them in a variable expression. For function arguments,
+# is to enclose them in an expression. For function arguments,
# neither the backslash nor the dollar sign act as escape character.
.if exists(\.)
. error
@@ -27,7 +27,7 @@
. error
.endif
-# The argument to the function can have several variable expressions.
+# The argument to the function can have several expressions.
# See cond-func.mk for the characters that cannot be used directly.
.if !exists(${.PARSEDIR}/${.PARSEFILE})
. error
@@ -38,6 +38,11 @@
. error
.endif
+# Expressions in the argument of a function call don't have to be defined.
+.if exists(${UNDEF})
+. error
+.endif
+
# The exists function does not really look up the file in the file system,
# instead it uses a cache that is preloaded very early, before parsing the
# first makefile. At that time, the file did not exist yet.
diff --git a/contrib/bmake/unit-tests/cond-func-make.exp b/contrib/bmake/unit-tests/cond-func-make.exp
index 922203b72cbf..ab25dfc5b553 100644
--- a/contrib/bmake/unit-tests/cond-func-make.exp
+++ b/contrib/bmake/unit-tests/cond-func-make.exp
@@ -1,3 +1,4 @@
+make: cond-func-make.mk:24: warning: Unfinished character list in pattern argument "[" to function "make"
: via-cmdline
: via-dot-makeflags
exit status 0
diff --git a/contrib/bmake/unit-tests/cond-func-make.mk b/contrib/bmake/unit-tests/cond-func-make.mk
index d75b69bcf98f..8903f9c0e723 100644
--- a/contrib/bmake/unit-tests/cond-func-make.mk
+++ b/contrib/bmake/unit-tests/cond-func-make.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-func-make.mk,v 1.3 2020/09/25 20:11:06 rillig Exp $
+# $NetBSD: cond-func-make.mk,v 1.7 2025/06/28 22:39:28 rillig Exp $
#
# Tests for the make() function in .if conditions, which tests whether
# the argument has been passed as a target via the command line or later
@@ -20,5 +20,15 @@
. error
.endif
+# expect+1: warning: Unfinished character list in pattern argument "[" to function "make"
+.if make([)
+. error
+.endif
+
+# Expressions in the argument of a function call don't have to be defined.
+.if make(${UNDEF})
+. error
+.endif
+
via-cmdline via-dot-makeflags:
: $@
diff --git a/contrib/bmake/unit-tests/cond-func-target.mk b/contrib/bmake/unit-tests/cond-func-target.mk
index 62266839df9e..2216997e4393 100644
--- a/contrib/bmake/unit-tests/cond-func-target.mk
+++ b/contrib/bmake/unit-tests/cond-func-target.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-func-target.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-func-target.mk,v 1.5 2025/01/10 23:00:38 rillig Exp $
#
# Tests for the target() function in .if conditions.
@@ -34,5 +34,10 @@ target:
. error
.endif
+# Expressions in the argument of a function call don't have to be defined.
+.if target(${UNDEF})
+. error
+.endif
+
all:
@:;
diff --git a/contrib/bmake/unit-tests/cond-func.exp b/contrib/bmake/unit-tests/cond-func.exp
index d0663ea68647..77f65e77d46c 100644
--- a/contrib/bmake/unit-tests/cond-func.exp
+++ b/contrib/bmake/unit-tests/cond-func.exp
@@ -1,12 +1,13 @@
-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 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: cond-func.mk:37: Missing ")" after argument "A" for "defined"
+make: cond-func.mk:53: Missing ")" after argument "A" for "defined"
+make: cond-func.mk:57: Missing ")" after argument "A" for "defined"
+make: cond-func.mk:91: Unknown operator "&"
+make: cond-func.mk:107: A plain function name is parsed as defined(...).
+make: cond-func.mk:115: A plain function name is parsed as defined(...).
+make: cond-func.mk:126: Symbols may start with a function name.
+make: cond-func.mk:132: Symbols may start with a function name.
+make: cond-func.mk:138: Missing ")" after argument "" for "defined"
+make: cond-func.mk:145: Missing ")" after argument "${:UVARNAME}.param" 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 959367f5c6ab..50b3f83cd4cb 100644
--- a/contrib/bmake/unit-tests/cond-func.mk
+++ b/contrib/bmake/unit-tests/cond-func.mk
@@ -1,12 +1,12 @@
-# $NetBSD: cond-func.mk,v 1.11 2022/01/07 19:30:17 rillig Exp $
+# $NetBSD: cond-func.mk,v 1.19 2025/06/28 22:39:28 rillig Exp $
#
# Tests for those parts of the functions in .if conditions that are common
# among several functions.
#
-# The below test uses the function defined(...) since it has no side-effects,
-# the other functions (except empty(...)) would work equally well. The
-# function empty is special because it uses a different parsing algorithm for
-# its argument.
+# The below test uses the 'defined' function since it has no side-effects.
+# The other functions would work equally well, except for 'empty', which
+# parses its argument differently from the other functions.
+#
DEF= defined
${:UA B}= variable name with spaces
@@ -33,11 +33,12 @@ ${VARNAME_UNBALANCED_BRACES}= variable name with unbalanced braces
.endif
# The argument of a function must not directly contain whitespace.
+# expect+1: Missing ")" after argument "A" for "defined"
.if !defined(A B)
. error
.endif
-# If necessary, the whitespace can be generated by a variable expression.
+# If necessary, the whitespace can be generated by an expression.
.if !defined(${:UA B})
. error
.endif
@@ -48,9 +49,11 @@ ${VARNAME_UNBALANCED_BRACES}= variable name with unbalanced braces
#
# It's not entirely clear why these characters are forbidden.
# The most plausible reason seems to be typo detection.
+# expect+1: Missing ")" after argument "A" for "defined"
.if !defined(A&B)
. error
.endif
+# expect+1: Missing ")" after argument "A" for "defined"
.if !defined(A|B)
. error
.endif
@@ -74,24 +77,24 @@ ${VARNAME_UNBALANCED_BRACES}= variable name with unbalanced braces
# There may be spaces around the operators and parentheses, and even
# inside the parentheses. The spaces inside the parentheses are not
-# allowed for the empty() function (see cond-func-empty.mk), therefore
+# allowed for the 'empty' function (see cond-func-empty.mk), therefore
# they are typically omitted for the other functions as well.
.if ! defined ( DEF )
. error
.endif
-# The following condition is interpreted as defined(A) && defined(B).
-# In lack of a function call expression, each kind of .if directive has a
+# Before cond.c 1.366 from 2024-07-06, the following condition was
+# interpreted as defined(A) && defined(B). Each kind of .if directive has a
# default function that is called when a bare word is parsed. For the plain
-# .if directive, this function is defined(); see "struct If ifs" in cond.c.
+# .if directive, this function is 'defined'; see "struct If ifs" in cond.c.
+# expect+1: Unknown operator "&"
.if A&B
. error
.endif
+# The empty variable is never defined.
.if defined()
. error
-.else
-. info The empty variable is never defined.
.endif
# The plain word 'defined' is interpreted as 'defined(defined)', see
@@ -100,6 +103,7 @@ ${VARNAME_UNBALANCED_BRACES}= variable name with unbalanced braces
.if defined
. error
.else
+# expect+1: A plain function name is parsed as defined(...).
. info A plain function name is parsed as defined(...).
.endif
@@ -107,6 +111,7 @@ ${VARNAME_UNBALANCED_BRACES}= variable name with unbalanced braces
# is interpreted as 'defined(defined)', and the condition evaluates to true.
defined= # defined but empty
.if defined
+# expect+1: A plain function name is parsed as defined(...).
. info A plain function name is parsed as defined(...).
.else
. error
@@ -117,19 +122,28 @@ defined= # defined but empty
.if defined-var
. error
.else
+# expect+1: Symbols may start with a function name.
. info Symbols may start with a function name.
.endif
defined-var= # defined but empty
.if defined-var
+# expect+1: Symbols may start with a function name.
. info Symbols may start with a function name.
.else
. error
.endif
-# Missing closing parenthesis when parsing the function argument.
+# expect+1: Missing ")" after argument "" for "defined"
.if defined(
. error
.else
. error
.endif
+
+# expect+1: Missing ")" after argument "${:UVARNAME}.param" for "defined"
+.if defined(${:UVARNAME}.param extra)
+. error
+.else
+. error
+.endif
diff --git a/contrib/bmake/unit-tests/cond-late.exp b/contrib/bmake/unit-tests/cond-late.exp
index e179e8c74cc4..e4b3fdac85f4 100644
--- a/contrib/bmake/unit-tests/cond-late.exp
+++ b/contrib/bmake/unit-tests/cond-late.exp
@@ -1,4 +1,9 @@
-make: Bad conditional expression ' != "no"' in ' != "no"?:'
+make: cond-late.mk:29: Bad condition
+ while evaluating condition " != "no""
+ while evaluating variable "VAR" with value "${${UNDEF} != "no":?:}"
+ in make[1] in directory "<curdir>"
+make: Fatal errors encountered -- cannot continue
+make: stopped making "do-parse-time" in unit-tests
yes
no
exit status 0
diff --git a/contrib/bmake/unit-tests/cond-late.mk b/contrib/bmake/unit-tests/cond-late.mk
index 4df3df2cf1d4..154617f4f2b2 100644
--- a/contrib/bmake/unit-tests/cond-late.mk
+++ b/contrib/bmake/unit-tests/cond-late.mk
@@ -1,11 +1,12 @@
-# $NetBSD: cond-late.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $
+# $NetBSD: cond-late.mk,v 1.10 2025/06/30 21:44:39 rillig Exp $
#
-# Using the :? modifier, variable expressions can contain conditional
+# Using the :? modifier, expressions can contain conditional
# expressions that are evaluated late, at expansion time.
#
-# Any variables appearing in these
-# conditions are expanded before parsing the condition. This is
-# different from many other places.
+# Any expressions appearing in these conditions are expanded before parsing
+# the condition. This is different from conditions in .if directives, where
+# expressions are evaluated individually and only as far as necessary, see
+# cond-short.mk.
#
# Because of this, variables that are used in these lazy conditions
# should not contain double-quotes, or the parser will probably fail.
@@ -14,18 +15,26 @@
# actually interpreted as these operators. This is demonstrated below.
#
-all: cond-literal
+all: parse-time cond-literal
+
+parse-time: .PHONY
+ @${MAKE} -f ${MAKEFILE} do-parse-time || true
COND.true= "yes" == "yes"
COND.false= "yes" != "yes"
+.if make(do-parse-time)
+VAR= ${${UNDEF} != "no":?:}
+# expect+1: Bad condition
+. if empty(VAR:Mpattern)
+. endif
+.endif
+
# If the order of evaluation were to change to first parse the condition
# and then expand the variables, the output would change from the
# current "yes no" to "yes yes", since both variables are non-empty.
+# expect: yes
+# expect: no
cond-literal:
@echo ${ ${COND.true} :?yes:no}
@echo ${ ${COND.false} :?yes:no}
-
-VAR+= ${${UNDEF} != "no":?:}
-.if empty(VAR:Mpattern)
-.endif
diff --git a/contrib/bmake/unit-tests/cond-op-and-lint.exp b/contrib/bmake/unit-tests/cond-op-and-lint.exp
index 8817fd0d658b..74ac32e205e6 100644
--- a/contrib/bmake/unit-tests/cond-op-and-lint.exp
+++ b/contrib/bmake/unit-tests/cond-op-and-lint.exp
@@ -1,4 +1,4 @@
-make: "cond-op-and-lint.mk" line 9: Unknown operator '&'
+make: cond-op-and-lint.mk:10: Unknown operator "&"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-op-and-lint.mk b/contrib/bmake/unit-tests/cond-op-and-lint.mk
index 6262339016f5..0daa3a728f86 100644
--- a/contrib/bmake/unit-tests/cond-op-and-lint.mk
+++ b/contrib/bmake/unit-tests/cond-op-and-lint.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-op-and-lint.mk,v 1.1 2020/11/08 23:54:28 rillig Exp $
+# $NetBSD: cond-op-and-lint.mk,v 1.3 2025/06/28 22:39:28 rillig Exp $
#
# Tests for the && operator in .if conditions, in lint mode.
@@ -6,6 +6,7 @@
# The '&' operator is not allowed in lint mode.
# It is not used in practice anyway.
+# expect+1: Unknown operator "&"
.if 0 & 0
. error
.else
diff --git a/contrib/bmake/unit-tests/cond-op-and.exp b/contrib/bmake/unit-tests/cond-op-and.exp
index cd6b03a8359e..b3e45ea1767e 100644
--- a/contrib/bmake/unit-tests/cond-op-and.exp
+++ b/contrib/bmake/unit-tests/cond-op-and.exp
@@ -1,7 +1,11 @@
-make: "cond-op-and.mk" line 36: Malformed conditional (0 || (${DEF} && ${UNDEF}))
-make: "cond-op-and.mk" line 40: Malformed conditional (0 || (${UNDEF} && ${UNDEF}))
-make: "cond-op-and.mk" line 42: Malformed conditional (0 || (!${UNDEF} && ${UNDEF}))
-make: "cond-op-and.mk" line 71: Malformed conditional (0 &&& 0)
+make: cond-op-and.mk:36: Variable "UNDEF" is undefined
+make: cond-op-and.mk:41: Variable "UNDEF" is undefined
+make: cond-op-and.mk:44: Variable "UNDEF" is undefined
+make: cond-op-and.mk:60: Unknown operator "&"
+make: cond-op-and.mk:66: Unknown operator "&"
+make: cond-op-and.mk:72: Unknown operator "&"
+make: cond-op-and.mk:78: Unknown operator "&"
+make: cond-op-and.mk:87: Unknown operator "&"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-op-and.mk b/contrib/bmake/unit-tests/cond-op-and.mk
index 83386ed77de4..cb84e39e9217 100644
--- a/contrib/bmake/unit-tests/cond-op-and.mk
+++ b/contrib/bmake/unit-tests/cond-op-and.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-op-and.mk,v 1.6 2021/12/10 19:14:35 rillig Exp $
+# $NetBSD: cond-op-and.mk,v 1.13 2025/06/28 22:39:28 rillig Exp $
#
# Tests for the && operator in .if conditions.
@@ -25,20 +25,22 @@
.endif
# When an outer condition makes the inner '&&' condition irrelevant, neither
-# of its operands must be evaluated.
-#
+# of its operands is evaluated.
.if 1 || (${UNDEF} && ${UNDEF})
.endif
# Test combinations of outer '||' with inner '&&', to ensure that the operands
# of the inner '&&' are only evaluated if necessary.
DEF= defined
+# expect+1: Variable "UNDEF" is undefined
.if 0 || (${DEF} && ${UNDEF})
.endif
.if 0 || (!${DEF} && ${UNDEF})
.endif
+# expect+1: Variable "UNDEF" is undefined
.if 0 || (${UNDEF} && ${UNDEF})
.endif
+# expect+1: Variable "UNDEF" is undefined
.if 0 || (!${UNDEF} && ${UNDEF})
.endif
.if 1 || (${DEF} && ${UNDEF})
@@ -54,23 +56,44 @@ DEF= defined
# The && operator may be abbreviated as &. This is not widely known though
# and is also not documented in the manual page.
+# expect+1: Unknown operator "&"
.if 0 & 0
. error
+.else
+. error
.endif
+# expect+1: Unknown operator "&"
.if 1 & 0
. error
+.else
+. error
.endif
+# expect+1: Unknown operator "&"
.if 0 & 1
. error
+.else
+. error
.endif
+# expect+1: Unknown operator "&"
.if !(1 & 1)
. error
+.else
+. error
.endif
-# There is no operator &&&.
+# There is no operator '&&&'. The first two '&&' form an operator, the third
+# '&' forms the next (incomplete) token.
+# expect+1: Unknown operator "&"
.if 0 &&& 0
. error
+.else
+. error
.endif
-all:
- @:;
+# The '&&' operator must be preceded by whitespace, otherwise it becomes part
+# of the preceding bare word. The condition starts with a digit and is thus
+# parsed as '"1&&" != "" && 1'.
+.if 1&& && 1
+.else
+. error
+.endif
diff --git a/contrib/bmake/unit-tests/cond-op-not.exp b/contrib/bmake/unit-tests/cond-op-not.exp
index 440670ca7249..f0dfb3b757b1 100644
--- a/contrib/bmake/unit-tests/cond-op-not.exp
+++ b/contrib/bmake/unit-tests/cond-op-not.exp
@@ -1,9 +1,9 @@
-make: "cond-op-not.mk" line 29: Not empty evaluates to true.
-make: "cond-op-not.mk" line 37: Not space evaluates to false.
-make: "cond-op-not.mk" line 41: Not 0 evaluates to true.
-make: "cond-op-not.mk" line 49: Not 1 evaluates to false.
-make: "cond-op-not.mk" line 55: Not word evaluates to false.
-make: "cond-op-not.mk" line 59: Malformed conditional (!)
+make: cond-op-not.mk:30: Not empty evaluates to true.
+make: cond-op-not.mk:39: Not space evaluates to false.
+make: cond-op-not.mk:44: Not 0 evaluates to true.
+make: cond-op-not.mk:53: Not 1 evaluates to false.
+make: cond-op-not.mk:60: Not word evaluates to false.
+make: cond-op-not.mk:65: Malformed conditional "!"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-op-not.mk b/contrib/bmake/unit-tests/cond-op-not.mk
index ffd5bc89e4bf..393d68675fca 100644
--- a/contrib/bmake/unit-tests/cond-op-not.mk
+++ b/contrib/bmake/unit-tests/cond-op-not.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-op-not.mk,v 1.7 2021/01/19 17:49:13 rillig Exp $
+# $NetBSD: cond-op-not.mk,v 1.10 2025/06/28 22:39:28 rillig Exp $
#
# Tests for the ! operator in .if conditions, which negates its argument.
@@ -26,6 +26,7 @@
.endif
.if !${:U}
+# expect+1: Not empty evaluates to true.
. info Not empty evaluates to true.
.else
. info Not empty evaluates to false.
@@ -34,10 +35,12 @@
.if !${:U }
. info Not space evaluates to true.
.else
+# expect+1: Not space evaluates to false.
. info Not space evaluates to false.
.endif
.if !${:U0}
+# expect+1: Not 0 evaluates to true.
. info Not 0 evaluates to true.
.else
. info Not 0 evaluates to false.
@@ -46,16 +49,19 @@
.if !${:U1}
. info Not 1 evaluates to true.
.else
+# expect+1: Not 1 evaluates to false.
. info Not 1 evaluates to false.
.endif
.if !${:Uword}
. info Not word evaluates to true.
.else
+# expect+1: Not word evaluates to false.
. info Not word evaluates to false.
.endif
# A single exclamation mark is a parse error.
+# expect+1: Malformed conditional "!"
.if !
. error
.else
diff --git a/contrib/bmake/unit-tests/cond-op-or-lint.exp b/contrib/bmake/unit-tests/cond-op-or-lint.exp
index 8abae99b6c4c..32d39dead9a1 100644
--- a/contrib/bmake/unit-tests/cond-op-or-lint.exp
+++ b/contrib/bmake/unit-tests/cond-op-or-lint.exp
@@ -1,4 +1,4 @@
-make: "cond-op-or-lint.mk" line 9: Unknown operator '|'
+make: cond-op-or-lint.mk:10: Unknown operator "|"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-op-or-lint.mk b/contrib/bmake/unit-tests/cond-op-or-lint.mk
index aa29e9a6c2f2..1efc5d94cbf2 100644
--- a/contrib/bmake/unit-tests/cond-op-or-lint.mk
+++ b/contrib/bmake/unit-tests/cond-op-or-lint.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-op-or-lint.mk,v 1.1 2020/11/08 23:54:28 rillig Exp $
+# $NetBSD: cond-op-or-lint.mk,v 1.3 2025/06/28 22:39:28 rillig Exp $
#
# Tests for the || operator in .if conditions, in lint mode.
@@ -6,6 +6,7 @@
# The '|' operator is not allowed in lint mode.
# It is not used in practice anyway.
+# expect+1: Unknown operator "|"
.if 0 | 0
. error
.else
diff --git a/contrib/bmake/unit-tests/cond-op-or.exp b/contrib/bmake/unit-tests/cond-op-or.exp
index 43b9a5438a31..fe42ffd6b310 100644
--- a/contrib/bmake/unit-tests/cond-op-or.exp
+++ b/contrib/bmake/unit-tests/cond-op-or.exp
@@ -1,7 +1,11 @@
-make: "cond-op-or.mk" line 46: Malformed conditional (1 && (!${DEF} || ${UNDEF}))
-make: "cond-op-or.mk" line 48: Malformed conditional (1 && (${UNDEF} || ${UNDEF}))
-make: "cond-op-or.mk" line 50: Malformed conditional (1 && (!${UNDEF} || ${UNDEF}))
-make: "cond-op-or.mk" line 71: Malformed conditional (0 ||| 0)
+make: cond-op-or.mk:36: Variable "UNDEF" is undefined
+make: cond-op-or.mk:41: Variable "UNDEF" is undefined
+make: cond-op-or.mk:44: Variable "UNDEF" is undefined
+make: cond-op-or.mk:60: Unknown operator "|"
+make: cond-op-or.mk:66: Unknown operator "|"
+make: cond-op-or.mk:72: Unknown operator "|"
+make: cond-op-or.mk:78: Unknown operator "|"
+make: cond-op-or.mk:87: Unknown operator "|"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-op-or.mk b/contrib/bmake/unit-tests/cond-op-or.mk
index 0b7ac55e6c35..1c6e081dcac1 100644
--- a/contrib/bmake/unit-tests/cond-op-or.mk
+++ b/contrib/bmake/unit-tests/cond-op-or.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-op-or.mk,v 1.8 2021/12/10 19:14:35 rillig Exp $
+# $NetBSD: cond-op-or.mk,v 1.15 2025/06/28 22:39:28 rillig Exp $
#
# Tests for the || operator in .if conditions.
@@ -25,52 +25,75 @@
.endif
# When an outer condition makes the inner '||' condition irrelevant, neither
-# of its operands must be evaluated. This had been wrong in cond.c 1.283 from
-# 2021-12-09 and was reverted in cond.c 1.284 an hour later.
+# of its operands is evaluated.
.if 0 && (!defined(UNDEF) || ${UNDEF})
.endif
# Test combinations of outer '&&' with inner '||', to ensure that the operands
-# of the inner '||' is only evaluated if necessary.
+# of the inner '||' are only evaluated if necessary.
DEF= defined
-.if 0 && (${DEF} || ${UNDEF})
+# expect+1: Variable "UNDEF" is undefined
+.if 1 && (!${DEF} || ${UNDEF})
.endif
-.if 0 && (!${DEF} || ${UNDEF})
+.if 1 && (${DEF} || ${UNDEF})
.endif
-.if 0 && (${UNDEF} || ${UNDEF})
+# expect+1: Variable "UNDEF" is undefined
+.if 1 && (!${UNDEF} || ${UNDEF})
.endif
-.if 0 && (!${UNDEF} || ${UNDEF})
+# expect+1: Variable "UNDEF" is undefined
+.if 1 && (${UNDEF} || ${UNDEF})
.endif
-.if 1 && (${DEF} || ${UNDEF})
+.if 0 && (!${DEF} || ${UNDEF})
.endif
-.if 1 && (!${DEF} || ${UNDEF})
+.if 0 && (${DEF} || ${UNDEF})
.endif
-.if 1 && (${UNDEF} || ${UNDEF})
+.if 0 && (!${UNDEF} || ${UNDEF})
.endif
-.if 1 && (!${UNDEF} || ${UNDEF})
+.if 0 && (${UNDEF} || ${UNDEF})
.endif
# The || operator may be abbreviated as |. This is not widely known though
# and is also not documented in the manual page.
+# expect+1: Unknown operator "|"
.if 0 | 0
. error
+.else
+. error
.endif
+# expect+1: Unknown operator "|"
.if !(1 | 0)
. error
+.else
+. error
.endif
+# expect+1: Unknown operator "|"
.if !(0 | 1)
. error
+.else
+. error
.endif
+# expect+1: Unknown operator "|"
.if !(1 | 1)
. error
+.else
+. error
.endif
-# There is no operator |||.
+# There is no operator '|||'. The first two '||' form an operator, the third
+# '|' forms the next (incomplete) token.
+# expect+1: Unknown operator "|"
.if 0 ||| 0
. error
+.else
+. error
.endif
-all:
- @:;
+# The '||' operator must be preceded by whitespace, otherwise it becomes part
+# of the preceding bare word. The condition starts with a digit and is thus
+# parsed as '"0||" != "" || 0'.
+.if 0|| || 0
+.else
+. error
+.endif
diff --git a/contrib/bmake/unit-tests/cond-op-parentheses.exp b/contrib/bmake/unit-tests/cond-op-parentheses.exp
index 63f7b19570b5..f8f9efc00772 100644
--- a/contrib/bmake/unit-tests/cond-op-parentheses.exp
+++ b/contrib/bmake/unit-tests/cond-op-parentheses.exp
@@ -1,7 +1,7 @@
-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: cond-op-parentheses.mk:22: Comparison with ">" requires both operands "3" and "(2" to be numeric
+make: cond-op-parentheses.mk:25: Malformed conditional "(3) > 2"
+make: cond-op-parentheses.mk:44: Malformed conditional "("
+make: cond-op-parentheses.mk:58: Malformed conditional ")"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" 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 b790f8bec330..17d6504ede9e 100644
--- a/contrib/bmake/unit-tests/cond-op-parentheses.mk
+++ b/contrib/bmake/unit-tests/cond-op-parentheses.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-op-parentheses.mk,v 1.5 2022/01/22 21:50:41 rillig Exp $
+# $NetBSD: cond-op-parentheses.mk,v 1.9 2025/06/28 22:39:28 rillig Exp $
#
# Tests for parentheses in .if conditions, which group expressions to override
# the precedence of the operators '!', '&&' and '||'. Parentheses cannot be
@@ -15,10 +15,13 @@
# 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 !=
+#
+# XXX: It's inconsistent that the right operand has unbalanced parentheses.
+#
+# expect+1: Comparison with ">" requires both operands "3" and "(2" to be numeric
.if 3 > (2)
.endif
-# expect+1: Malformed conditional ((3) > 2)
+# expect+1: Malformed conditional "(3) > 2"
.if (3) > 2
.endif
@@ -37,6 +40,7 @@
.endif
# An unbalanced opening parenthesis is a parse error.
+# expect+1: Malformed conditional "("
.if (
. error
.else
@@ -50,6 +54,7 @@
# 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.
+# expect+1: Malformed conditional ")"
.if )
. error
.else
diff --git a/contrib/bmake/unit-tests/cond-op.exp b/contrib/bmake/unit-tests/cond-op.exp
index b8f6a4301819..692698b91a96 100644
--- a/contrib/bmake/unit-tests/cond-op.exp
+++ b/contrib/bmake/unit-tests/cond-op.exp
@@ -1,22 +1,21 @@
-make: "cond-op.mk" line 50: Malformed conditional ("!word" == !word)
-make: "cond-op.mk" line 76: Malformed conditional (0 ${ERR::=evaluated})
-make: "cond-op.mk" line 80: A misplaced expression after 0 is not evaluated.
-make: "cond-op.mk" line 84: Malformed conditional (1 ${ERR::=evaluated})
-make: "cond-op.mk" line 88: A misplaced expression after 1 is not evaluated.
-make: "cond-op.mk" line 92: Parsing continues until here.
-make: "cond-op.mk" line 95: A B C => (A || B) && C A || B && C A || (B && C)
-make: "cond-op.mk" line 102: 0 0 0 => 0 0 0
-make: "cond-op.mk" line 102: 0 0 1 => 0 0 0
-make: "cond-op.mk" line 102: 0 1 0 => 0 0 0
-make: "cond-op.mk" line 102: 0 1 1 => 1 1 1
-make: "cond-op.mk" line 102: 1 0 0 => 0 1 1
-make: "cond-op.mk" line 102: 1 0 1 => 1 1 1
-make: "cond-op.mk" line 102: 1 1 0 => 0 1 1
-make: "cond-op.mk" line 102: 1 1 1 => 1 1 1
-make: "cond-op.mk" line 113: Malformed conditional (1 &&)
-make: "cond-op.mk" line 121: Malformed conditional (0 &&)
-make: "cond-op.mk" line 129: Malformed conditional (1 ||)
-make: "cond-op.mk" line 138: Malformed conditional (0 ||)
+make: cond-op.mk:51: Malformed conditional ""!word" == !word"
+make: cond-op.mk:72: Malformed conditional "0 ${ERR::=evaluated}"
+make: cond-op.mk:77: A misplaced expression after 0 is not evaluated.
+make: cond-op.mk:82: Malformed conditional "1 ${ERR::=evaluated}"
+make: cond-op.mk:87: A misplaced expression after 1 is not evaluated.
+make: cond-op.mk:93: A B C => (A || B) && C A || B && C A || (B && C)
+make: cond-op.mk:108: 0 0 0 => 0 0 0
+make: cond-op.mk:108: 0 0 1 => 0 0 0
+make: cond-op.mk:108: 0 1 0 => 0 0 0
+make: cond-op.mk:108: 0 1 1 => 1 1 1
+make: cond-op.mk:108: 1 0 0 => 0 1 1
+make: cond-op.mk:108: 1 0 1 => 1 1 1
+make: cond-op.mk:108: 1 1 0 => 0 1 1
+make: cond-op.mk:108: 1 1 1 => 1 1 1
+make: cond-op.mk:120: Malformed conditional "1 &&"
+make: cond-op.mk:129: Malformed conditional "0 &&"
+make: cond-op.mk:138: Malformed conditional "1 ||"
+make: cond-op.mk:148: Malformed conditional "0 ||"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-op.mk b/contrib/bmake/unit-tests/cond-op.mk
index c3ab09f7709a..974cb938065c 100644
--- a/contrib/bmake/unit-tests/cond-op.mk
+++ b/contrib/bmake/unit-tests/cond-op.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-op.mk,v 1.15 2021/12/10 23:12:44 rillig Exp $
+# $NetBSD: cond-op.mk,v 1.18 2025/06/28 22:39:28 rillig Exp $
#
# Tests for operators like &&, ||, ! in .if conditions.
#
@@ -47,6 +47,7 @@
# appear unquoted. If any, it must be enclosed in quotes.
# In any case, it is not interpreted as a negation of an unquoted string.
# See CondParser_String.
+# expect+1: Malformed conditional ""!word" == !word"
.if "!word" == !word
. error
.endif
@@ -66,32 +67,29 @@
# nevertheless, since CondParser_Or and CondParser_And asked for the expanded
# next token, even though in this position of the condition, only comparison
# operators, TOK_AND, TOK_OR or TOK_RPAREN are allowed.
-#
-#
-#
-#
-#
-#
.undef ERR
+# expect+1: Malformed conditional "0 ${ERR::=evaluated}"
.if 0 ${ERR::=evaluated}
. error
.endif
.if ${ERR:Uundefined} == undefined
+# expect+1: A misplaced expression after 0 is not evaluated.
. info A misplaced expression after 0 is not evaluated.
.endif
.undef ERR
+# expect+1: Malformed conditional "1 ${ERR::=evaluated}"
.if 1 ${ERR::=evaluated}
. error
.endif
.if ${ERR:Uundefined} == undefined
+# expect+1: A misplaced expression after 1 is not evaluated.
. info A misplaced expression after 1 is not evaluated.
.endif
-# Just in case that parsing should ever stop on the first error.
-.info Parsing continues until here.
# Demonstration that '&&' has higher precedence than '||'.
+# expect+1: A B C => (A || B) && C A || B && C A || (B && C)
.info A B C => (A || B) && C A || B && C A || (B && C)
.for a in 0 1
. for b in 0 1
@@ -99,6 +97,14 @@
. for r1 in ${ ($a || $b) && $c :?1:0}
. for r2 in ${ $a || $b && $c :?1:0}
. for r3 in ${ $a || ($b && $c) :?1:0}
+# expect+8: 0 0 0 => 0 0 0
+# expect+7: 0 0 1 => 0 0 0
+# expect+6: 0 1 0 => 0 0 0
+# expect+5: 0 1 1 => 1 1 1
+# expect+4: 1 0 0 => 0 1 1
+# expect+3: 1 0 1 => 1 1 1
+# expect+2: 1 1 0 => 0 1 1
+# expect+1: 1 1 1 => 1 1 1
. info $a $b $c => ${r1} ${r2} ${r3}
. endfor
. endfor
@@ -110,6 +116,7 @@
# This condition is obviously malformed. It is properly detected and also
# was properly detected before 2021-01-19, but only because the left hand
# side of the '&&' evaluated to true.
+# expect+1: Malformed conditional "1 &&"
.if 1 &&
. error
.else
@@ -118,6 +125,7 @@
# This obviously malformed condition was not detected as such before cond.c
# 1.238 from 2021-01-19.
+# expect+1: Malformed conditional "0 &&"
.if 0 &&
. error
.else
@@ -126,6 +134,7 @@
# This obviously malformed condition was not detected as such before cond.c
# 1.238 from 2021-01-19.
+# expect+1: Malformed conditional "1 ||"
.if 1 ||
. error
.else
@@ -135,6 +144,7 @@
# This condition is obviously malformed. It is properly detected and also
# was properly detected before 2021-01-19, but only because the left hand
# side of the '||' evaluated to false.
+# expect+1: Malformed conditional "0 ||"
.if 0 ||
. error
.else
diff --git a/contrib/bmake/unit-tests/cond-short.exp b/contrib/bmake/unit-tests/cond-short.exp
index 2865dcb6ef33..4100e6e5ef2b 100644
--- a/contrib/bmake/unit-tests/cond-short.exp
+++ b/contrib/bmake/unit-tests/cond-short.exp
@@ -7,10 +7,7 @@ expected M pattern
expected or
expected or exists
expected or empty
-defined(V42) && ${V42} > 0: Ok
-defined(V66) && ( "${iV2}" < ${V42} ): Ok
-1 || ${iV1} < ${V42}: Ok
-1 || ${iV2:U2} < ${V42}: Ok
-0 || ${iV1} <= ${V42}: Ok
-0 || ${iV2:U2} < ${V42}: Ok
-exit status 0
+make: cond-short.mk:231: Comparison with "<" requires both operands "" and "42" to be numeric
+make: Fatal errors encountered -- cannot continue
+make: stopped making "all" in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/cond-short.mk b/contrib/bmake/unit-tests/cond-short.mk
index f4e8f87043b5..8c4c4140596e 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.19 2021/12/27 18:54:19 rillig Exp $
+# $NetBSD: cond-short.mk,v 1.24 2025/06/28 22:39:28 rillig Exp $
#
# Demonstrates that in conditions, the right-hand side of an && or ||
# is only evaluated if it can actually influence the result.
@@ -9,9 +9,9 @@
# Before 2020-06-28, the right-hand side of an && or || operator was always
# evaluated, which was wrong. In cond.c 1.69 and var.c 1.197 on 2015-10-11,
# Var_Parse got a new parameter named 'wantit'. Since then it would have been
-# possible to skip evaluation of irrelevant variable expressions and only
+# possible to skip evaluation of irrelevant expressions and only
# parse them. They were still evaluated though, the only difference to
-# relevant variable expressions was that in the irrelevant variable
+# relevant expressions was that in the irrelevant
# 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 the irrelevant expression was
@@ -135,28 +135,32 @@ VAR= # empty again, for the following tests
.elif ${echo "unexpected nested or" 1>&2 :L:sh}
.endif
-# make sure these do not cause complaint
-#.MAKEFLAGS: -dc
-# TODO: Rewrite this whole section and check all the conditions and variables.
-# Several of the assumptions are probably wrong here.
-# TODO: replace 'x=' with '.info' or '.error'.
-V42= 42
-iV1= ${V42}
-iV2= ${V66}
+NUMBER= 42
+INDIR_NUMBER= ${NUMBER}
+INDIR_UNDEF= ${UNDEF}
-.if defined(V42) && ${V42} > 0
-x= Ok
+.if defined(NUMBER) && ${NUMBER} > 0
.else
-x= Fail
+. error
.endif
-x!= echo 'defined(V42) && $${V42} > 0: $x' >&2; echo
-# With cond.c 1.76 from 2020-07-03, the following condition triggered a
-# warning: "String comparison operator should be either == or !=".
-# This was because the variable expression ${iV2} was defined, but the
-# contained variable V66 was undefined. The left-hand side of the comparison
-# therefore evaluated to the string "${V66}", which is obviously not a number.
+# Starting with var.c 1.226 from from 2020-07-02, the following condition
+# triggered a warning: "String comparison operator should be either == or !=".
+#
+# The left-hand side of the '&&' evaluated to false, which should have made
+# the right-hand side irrelevant.
+#
+# On the right-hand side of the '&&', the expression ${INDIR_UNDEF} was
+# defined and had the value '${UNDEF}', but the nested variable UNDEF was
+# undefined. The right hand side "${INDIR_UNDEF}" still needed to be parsed,
+# and in parse-only mode, the "value" of the parsed expression was the
+# uninterpreted variable value, in this case '${UNDEF}'. And even though the
+# right hand side of the '&&' should have been irrelevant, the two sides of
+# the comparison were still parsed and evaluated. Comparing these two values
+# numerically was not possible since the string '${UNDEF}' is not a number,
+# so the comparison fell back to string comparison, which then complained
+# about the '>' operator.
#
# This was fixed in cond.c 1.79 from 2020-07-09 by not evaluating irrelevant
# comparisons. Instead, they are only parsed and then discarded.
@@ -164,59 +168,94 @@ x!= echo 'defined(V42) && $${V42} > 0: $x' >&2; echo
# At that time, there was not enough debug logging to see the details in the
# -dA log. To actually see it, add debug logging at the beginning and end of
# Var_Parse.
-.if defined(V66) && ( ${iV2} < ${V42} )
-x= Fail
-.else
-x= Ok
+.if defined(UNDEF) && ${INDIR_UNDEF} < ${NUMBER}
+. error
+.endif
+# Adding a ':U' modifier to the irrelevant expression didn't help, as that
+# expression was only parsed, not evaluated. The resulting literal string
+# '${INDIR_UNDEF:U2}' was not numeric either, for the same reason as above.
+.if defined(UNDEF) && ${INDIR_UNDEF:U2} < ${NUMBER}
+. error
.endif
-# XXX: This condition doesn't match the one above. The quotes are missing
-# above. This is a crucial detail since without quotes, the variable
-# expression ${iV2} evaluates to "${V66}", and with quotes, it evaluates to ""
-# since undefined variables are allowed and expand to an empty string.
-x!= echo 'defined(V66) && ( "$${iV2}" < $${V42} ): $x' >&2; echo
-.if 1 || ${iV1} < ${V42}
-x= Ok
-.else
-x= Fail
+
+# Since cond.c 1.76 from 2020.06.28 and before var.c 1.225 from 2020.07.01,
+# the following snippet resulted in the error message 'Variable VAR is
+# recursive'. The condition '0' evaluated to false, which made the right-hand
+# side of the '&&' irrelevant. Back then, irrelevant condition parts were
+# still evaluated, but in "irrelevant mode", which allowed undefined variables
+# to occur in expressions. In this mode, the variable name 'VAR' was
+# unnecessarily evaluated, resulting in the expression '${VAR${:U1}}'. In
+# this expression, the variable name was 'VAR${:U1}', and of this variable
+# name, only the fixed part 'VAR' was evaluated, without the part '${:U1}'.
+# This partial evaluation led to the wrong error message about 'VAR' being
+# recursive.
+VAR= ${VAR${:U1}}
+.if 0 && !empty(VAR)
.endif
-x!= echo '1 || $${iV1} < $${V42}: $x' >&2; echo
-# With cond.c 1.76 from 2020-07-03, the following condition triggered a
-# warning: "String comparison operator should be either == or !=".
-# This was because the variable expression ${iV2} was defined, but the
-# contained variable V66 was undefined. The left-hand side of the comparison
-# therefore evaluated to the string "${V66}", which is obviously not a number.
+
+# Enclosing the expression in double quotes changes how that expression is
+# evaluated. In irrelevant expressions that are enclosed in double quotes,
+# expressions based on undefined variables are allowed and evaluate to an
+# empty string.
#
-# This was fixed in cond.c 1.79 from 2020-07-09 by not evaluating irrelevant
-# comparisons. Instead, they are only parsed and then discarded.
+# The manual page stated from at least 1993 on that irrelevant conditions were
+# not evaluated, but that was wrong. These conditions were evaluated, the
+# only difference was that undefined variables in them didn't trigger an
+# error. Since numeric conditions are quite rare, this subtle difference
+# didn't catch much attention, as most other conditions such as pattern
+# matches or equality comparisons worked fine and never produced error
+# messages.
+.if defined(UNDEF) && "${INDIR_UNDEF}" < ${NUMBER}
+. error
+.endif
+
+# Since the condition is relevant, the indirect undefined variable is
+# evaluated as usual, resolving nested undefined expressions to an empty
+# string.
#
-# At that time, there was not enough debug logging to see the details in the
-# -dA log. To actually see it, add debug logging at the beginning and end of
-# Var_Parse.
-.if 1 || ${iV2:U2} < ${V42}
-x= Ok
+# Comparing an empty string numerically is not possible, however, make has an
+# ugly hack in TryParseNumber that treats an empty string as a valid numerical
+# value, thus hiding bugs in the makefile.
+.if ${INDIR_UNDEF} < ${NUMBER}
+# only due to the ugly hack
+.else
+. error
+.endif
+
+# Due to the quotes around the left-hand side of the '<', the operand is
+# marked as a string, thus preventing a numerical comparison.
+#
+# expect+1: Comparison with "<" requires both operands "" and "42" to be numeric
+.if "${INDIR_UNDEF}" < ${NUMBER}
+. info yes
.else
-x= Fail
+. info no
.endif
-x!= echo '1 || $${iV2:U2} < $${V42}: $x' >&2; echo
-# the same expressions are fine when the lhs is expanded
-# ${iV1} expands to 42
-.if 0 || ${iV1} <= ${V42}
-x= Ok
+# The right-hand side of '||' is irrelevant and thus not evaluated.
+.if 1 || ${INDIR_NUMBER} < ${NUMBER}
.else
-x= Fail
+. error
.endif
-x!= echo '0 || $${iV1} <= $${V42}: $x' >&2; echo
-# ${iV2:U2} expands to 2
-.if 0 || ${iV2:U2} < ${V42}
-x= Ok
+# The right-hand side of '||' is relevant and thus evaluated normally.
+.if 0 || ${INDIR_NUMBER} < ${NUMBER}
+. error
+.endif
+
+# The right-hand side of '||' evaluates to an empty string, as the variable
+# 'INDIR_UNDEF' is defined, therefore the modifier ':U2' has no effect.
+# Comparing an empty string numerically is not possible, however, make has an
+# ugly hack in TryParseNumber that treats an empty string as a valid numerical
+# value, thus hiding bugs in the makefile.
+.if 0 || ${INDIR_UNDEF:U2} < ${NUMBER}
+# only due to the ugly hack
.else
-x= Fail
+. error
.endif
-x!= echo '0 || $${iV2:U2} < $${V42}: $x' >&2; echo
+
# The right-hand side of the '&&' is irrelevant since the left-hand side
# already evaluates to false. Before cond.c 1.79 from 2020-07-09, it was
@@ -229,8 +268,8 @@ x!= echo '0 || $${iV2:U2} < $${V42}: $x' >&2; echo
# Ensure that irrelevant conditions do not influence the result of the whole
# condition. As of cond.c 1.302 from 2021-12-11, an irrelevant function call
-# evaluates to true (see CondParser_FuncCall and CondParser_FuncCallEmpty), an
-# irrelevant comparison evaluates to false (see CondParser_Comparison).
+# evaluated to true (see CondParser_FuncCall and CondParser_FuncCallEmpty), an
+# irrelevant comparison evaluated to false (see CondParser_Comparison).
#
# An irrelevant true bubbles up to the outermost CondParser_And, where it is
# ignored. An irrelevant false bubbles up to the outermost CondParser_Or,
diff --git a/contrib/bmake/unit-tests/cond-token-number.exp b/contrib/bmake/unit-tests/cond-token-number.exp
index f078cb007323..a3b53c2dcd44 100644
--- a/contrib/bmake/unit-tests/cond-token-number.exp
+++ b/contrib/bmake/unit-tests/cond-token-number.exp
@@ -1,8 +1,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 89: End of the tests.
+make: cond-token-number.mk:16: Malformed conditional "-0"
+make: cond-token-number.mk:27: Malformed conditional "+0"
+make: cond-token-number.mk:38: Malformed conditional "!-1"
+make: cond-token-number.mk:49: Malformed conditional "!+1"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" 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 eef528f4b7c6..1d0f8d20287e 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.7 2022/01/02 02:57:39 rillig Exp $
+# $NetBSD: cond-token-number.mk,v 1.12 2025/06/28 22:39:28 rillig Exp $
#
# Tests for number tokens in .if conditions.
#
@@ -12,6 +12,7 @@
# accepted by the condition parser.
#
# See the ch_isdigit call in CondParser_String.
+# expect+1: Malformed conditional "-0"
.if -0
. error
.else
@@ -22,6 +23,7 @@
# accepted by the condition parser.
#
# See the ch_isdigit call in CondParser_String.
+# expect+1: Malformed conditional "+0"
.if +0
. error
.else
@@ -32,6 +34,7 @@
# accepted by the condition parser.
#
# See the ch_isdigit call in CondParser_String.
+# expect+1: Malformed conditional "!-1"
.if !-1
. error
.else
@@ -42,19 +45,20 @@
# accepted by the condition parser.
#
# See the ch_isdigit call in CondParser_String.
+# expect+1: Malformed conditional "!+1"
.if !+1
. error
.else
. error
.endif
-# When the number comes from a variable expression though, it may be signed.
+# When the number comes from an expression though, it may be signed.
# XXX: This is inconsistent.
.if ${:U+0}
. error
.endif
-# When the number comes from a variable expression though, it may be signed.
+# When the number comes from an expression though, it may be signed.
# XXX: This is inconsistent.
.if !${:U+1}
. error
@@ -85,7 +89,21 @@ HEX= dead
. error
.endif
-# Ensure that parsing continues until here.
-.info End of the tests.
+# Very small numbers round to 0.
+.if 12345e-400
+. error
+.endif
+.if 12345e-200
+.else
+. error
+.endif
+
+# Very large numbers round up to infinity on IEEE 754 implementations, or to
+# the largest representable number (VAX); in particular, make does not fall
+# back to checking whether a variable of that name is defined.
+.if 12345e400
+.else
+. error
+.endif
-all: # nothing
+all:
diff --git a/contrib/bmake/unit-tests/cond-token-plain.exp b/contrib/bmake/unit-tests/cond-token-plain.exp
index 90da7644bd9e..0b430eba7d39 100644
--- a/contrib/bmake/unit-tests/cond-token-plain.exp
+++ b/contrib/bmake/unit-tests/cond-token-plain.exp
@@ -1,19 +1,21 @@
CondParser_Eval: ${:Uvalue} != value
Comparing "value" != "value"
CondParser_Eval: ${:U} != "
+make: cond-token-plain.mk:19: Unfinished string literal """
Comparing "" != ""
CondParser_Eval: ${:U#hash} != "#hash"
Comparing "#hash" != "#hash"
CondParser_Eval: ${:U\\} != "\\
+make: cond-token-plain.mk:43: Unfinished string literal ""\\"
Comparing "\" != "\"
CondParser_Eval: ${:U#hash} != #hash
Comparing "#hash" != "#hash"
CondParser_Eval: 0 # This is treated as a comment, but why?
-CondParser_Eval: ${0 # comment :?yes:no} != no
-CondParser_Eval: 0 # comment
+CondParser_Eval: ${0 # comment:?yes:no} != no
+CondParser_Eval: 0 # comment
Comparing "no" != "no"
-CondParser_Eval: ${1 # comment :?yes:no} != yes
-CondParser_Eval: 1 # comment
+CondParser_Eval: ${1 # comment:?yes:no} != yes
+CondParser_Eval: 1 # comment
Comparing "yes" != "yes"
CondParser_Eval: ${UNDEF:Uundefined}!=undefined
Comparing "undefined" != "undefined"
@@ -27,36 +29,45 @@ Comparing "var&&name" != "var&&name"
CondParser_Eval: ${:Uvar}||name != "var||name"
Comparing "var||name" != "var||name"
CondParser_Eval: bare
-make: "cond-token-plain.mk" line 105: A bare word is treated like defined(...), and the variable 'bare' is not defined.
+make: cond-token-plain.mk:106: 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(...).
+make: cond-token-plain.mk:113: A bare word is treated like defined(...).
CondParser_Eval: V${:UA}R
-make: "cond-token-plain.mk" line 118: ok
+make: cond-token-plain.mk:121: ok
CondParser_Eval: V${UNDEF}AR
-make: "cond-token-plain.mk" line 126: Undefined variables in bare words expand to an empty string.
+make: cond-token-plain.mk:130: Undefined variables in bare words expand to an empty string.
CondParser_Eval: 0${:Ux00}
-make: "cond-token-plain.mk" line 134: Numbers can be composed from literals and variable expressions.
+make: cond-token-plain.mk:139: Numbers can be composed from literals and expressions.
CondParser_Eval: 0${:Ux01}
-make: "cond-token-plain.mk" line 138: Numbers can be composed from literals and variable expressions.
+make: cond-token-plain.mk:144: Numbers can be composed from literals and expressions.
CondParser_Eval: "" ==
-make: "cond-token-plain.mk" line 144: Missing right-hand side of operator '=='
+make: cond-token-plain.mk:151: Missing right-hand side of operator "=="
CondParser_Eval: == ""
-make: "cond-token-plain.mk" line 152: Malformed conditional (== "")
+make: cond-token-plain.mk:160: Malformed conditional "== """
CondParser_Eval: \\
-make: "cond-token-plain.mk" line 167: The variable '\\' is not defined.
+make: cond-token-plain.mk:176: The variable '\\' is not defined.
CondParser_Eval: \\
-make: "cond-token-plain.mk" line 172: Now the variable '\\' is defined.
+make: cond-token-plain.mk:182: Now the variable '\\' is defined.
CondParser_Eval: "unquoted\"quoted" != unquoted"quoted
Comparing "unquoted"quoted" != "unquoted"quoted"
CondParser_Eval: $$$$$$$$ != ""
+make: cond-token-plain.mk:197: Malformed conditional "$$$$$$$$ != """
CondParser_Eval: left == right
-make: "cond-token-plain.mk" line 195: Malformed conditional (left == right)
+make: cond-token-plain.mk:206: Malformed conditional "left == right"
CondParser_Eval: ${0:?:} || left == right
CondParser_Eval: 0
-make: "cond-token-plain.mk" line 201: Malformed conditional (${0:?:} || left == right)
+make: cond-token-plain.mk:212: 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: cond-token-plain.mk:217: Malformed conditional "left == right || ${0:?:}"
+make: cond-token-plain.mk:236: Malformed conditional "VAR.${IF_COUNT::+=1} != """
+make: cond-token-plain.mk:272: Unfinished backslash escape sequence
+ while evaluating condition " str == str\"
+make: cond-token-plain.mk:282: Unfinished backslash escape sequence
+ while evaluating condition " str == "str\"
+make: cond-token-plain.mk:282: Unfinished string literal ""str\"
+ while evaluating condition " str == "str\"
+make: cond-token-plain.mk:289: Unfinished string literal ""str"
+ while evaluating condition " str == "str"
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 1e9f30be9153..400af22f92d7 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.15 2021/12/30 02:14:55 rillig Exp $
+# $NetBSD: cond-token-plain.mk,v 1.23 2025/07/06 07:56:16 rillig Exp $
#
# Tests for plain tokens (that is, string literals without quotes)
# in .if conditions. These are also called bare words.
@@ -14,8 +14,8 @@
# condition since comment parsing is done in an early phase and removes the
# '#' and everything after it long before the condition parser gets to see it.
#
-# XXX: The error message is missing for this malformed condition.
-# The right-hand side of the comparison is just a '"', before unescaping.
+#
+# expect+1: Unfinished string literal """
.if ${:U} != "#hash"
. error
.endif
@@ -38,8 +38,8 @@
# comments are stripped in an earlier phase. See "case '#'" in
# CondParser_Token.
#
-# XXX: Missing error message for the malformed condition. The right-hand
-# side before unescaping is double-quotes, backslash, backslash.
+#
+# expect+1: Unfinished string literal ""\\"
.if ${:U\\} != "\\#hash"
. error
.endif
@@ -63,10 +63,10 @@
# anybody really use this? This is neither documented nor obvious since
# the '#' is escaped. It's much clearer to write a comment in the line
# above the condition.
-.if ${0 \# comment :?yes:no} != no
+.if ${0 \# comment:?yes:no} != no
. error
.endif
-.if ${1 \# comment :?yes:no} != yes
+.if ${1 \# comment:?yes:no} != yes
. error
.endif
@@ -89,7 +89,7 @@
# a coincidence that the '!' is both used in the '!=' comparison operator
# as well as for negating a comparison result.
#
-# The boolean operators '&' and '|' don't terminate a comparison operand.
+# The characters '&' and '|' are part of the comparison operand.
.if ${:Uvar}&&name != "var&&name"
. error
.endif
@@ -97,24 +97,27 @@
. error
.endif
-# A bare word may appear alone in a condition, without any comparison
-# operator. It is implicitly converted into defined(bare).
+# A bare word may occur alone in a condition, without any comparison
+# operator. It is interpreted as the function call 'defined(bare)'.
.if bare
. error
.else
+# expect+1: A bare word is treated like defined(...), and the variable 'bare' is not defined.
. info A bare word is treated like defined(...), and the variable $\
'bare' is not defined.
.endif
VAR= defined
.if VAR
+# expect+1: A bare word is treated like defined(...).
. info A bare word is treated like defined(...).
.else
. error
.endif
-# Bare words may be intermixed with variable expressions.
+# Bare words may be intermixed with expressions.
.if V${:UA}R
+# expect+1: ok
. info ok
.else
. error
@@ -123,6 +126,7 @@ VAR= defined
# In bare words, even undefined variables are allowed. Without the bare
# words, undefined variables are not allowed. That feels inconsistent.
.if V${UNDEF}AR
+# expect+1: Undefined variables in bare words expand to an empty string.
. info Undefined variables in bare words expand to an empty string.
.else
. error
@@ -131,16 +135,19 @@ VAR= defined
.if 0${:Ux00}
. error
.else
-. info Numbers can be composed from literals and variable expressions.
+# expect+1: Numbers can be composed from literals and expressions.
+. info Numbers can be composed from literals and expressions.
.endif
.if 0${:Ux01}
-. info Numbers can be composed from literals and variable expressions.
+# expect+1: Numbers can be composed from literals and expressions.
+. info Numbers can be composed from literals and expressions.
.else
. error
.endif
# If the right-hand side is missing, it's a parse error.
+# expect+1: Missing right-hand side of operator "=="
.if "" ==
. error
.else
@@ -149,6 +156,7 @@ VAR= defined
# If the left-hand side is missing, it's a parse error as well, but without
# a specific error message.
+# expect+1: Malformed conditional "== """
.if == ""
. error
.else
@@ -164,11 +172,13 @@ VAR= defined
.if \\
. error
.else
+# expect+1: The variable '\\' is not defined.
. info The variable '\\' is not defined.
.endif
${:U\\\\}= backslash
.if \\
+# expect+1: Now the variable '\\' is defined.
. info Now the variable '\\' is defined.
.else
. error
@@ -183,6 +193,7 @@ ${:U\\\\}= backslash
# FIXME: In CondParser_String, Var_Parse returns var_Error without a
# corresponding error message.
+# expect+1: Malformed conditional "$$$$$$$$ != """
.if $$$$$$$$ != ""
. error
.else
@@ -191,18 +202,18 @@ ${:U\\\\}= backslash
# In a condition in an .if directive, the left-hand side must not be an
# unquoted string literal.
-# expect+1: Malformed conditional (left == right)
+# expect+1: Malformed conditional "left == right"
.if left == right
.endif
-# Before cond.c 1.276 from 2021-09-21, a variable expression containing the
+# Before cond.c 1.276 from 2021-09-21, an expression containing the
# modifier ':?:' allowed unquoted string literals for the rest of the
# condition. This was an unintended implementation mistake.
-# expect+1: Malformed conditional (${0:?:} || left == right)
+# expect+1: Malformed conditional "${0:?:} || left == right"
.if ${0:?:} || left == right
.endif
# This affected only the comparisons after the expression, so the following
# was still a syntax error.
-# expect+1: Malformed conditional (left == right || ${0:?:})
+# expect+1: Malformed conditional "left == right || ${0:?:}"
.if left == right || ${0:?:}
.endif
@@ -221,7 +232,7 @@ ${:U\\\\}= backslash
# 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} != "")
+# expect+1: Malformed conditional "VAR.${IF_COUNT::+=1} != """
.if VAR.${IF_COUNT::+=1} != ""
. error
.else
@@ -234,7 +245,7 @@ ${:U\\\\}= backslash
# 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
+# the condition, see varmod-ifelse.mk. To pass an 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.
@@ -254,3 +265,29 @@ COND= VAR.$${MOD_COUNT::+=1}
. error
.endif
#.MAKEFLAGS: -d0
+
+
+# A trailing backslash in a bare word does not escape anything.
+# expect+1: Unfinished backslash escape sequence
+.if ${${:U str == str\\}:?yes:no}
+. error
+.else
+. error
+.endif
+
+# A trailing backslash in an unfinished string literal word does not escape
+# anything.
+# expect+2: Unfinished backslash escape sequence
+# expect+1: Unfinished string literal ""str\"
+.if ${${:U str == "str\\}:?yes:no}
+. error
+.else
+. error
+.endif
+
+# expect+1: Unfinished string literal ""str"
+.if ${${:U str == "str}:?yes:no}
+. error
+.else
+. error
+.endif
diff --git a/contrib/bmake/unit-tests/cond-token-string.exp b/contrib/bmake/unit-tests/cond-token-string.exp
index 1c0f086b464e..d31c0abda17d 100644
--- a/contrib/bmake/unit-tests/cond-token-string.exp
+++ b/contrib/bmake/unit-tests/cond-token-string.exp
@@ -1,18 +1,21 @@
-make: "cond-token-string.mk" line 13: Unknown modifier "Z"
-make: "cond-token-string.mk" line 13: Malformed conditional ("" != "${:Uvalue:Z}")
-make: "cond-token-string.mk" line 22: xvalue is not defined.
-make: "cond-token-string.mk" line 28: Malformed conditional (x${:Uvalue} == "")
-make: "cond-token-string.mk" line 37: Expected.
+make: cond-token-string.mk:14: Unknown modifier ":Z"
+ while evaluating "${:Uvalue:Z}"" with value "value"
+make: cond-token-string.mk:24: xvalue is not defined.
+make: cond-token-string.mk:31: Malformed conditional "x${:Uvalue} == """
+make: cond-token-string.mk:41: Expected.
CondParser_Eval: "UNDEF"
-make: "cond-token-string.mk" line 46: The string literal "UNDEF" is not empty.
+make: cond-token-string.mk:51: The string literal "UNDEF" is not empty.
CondParser_Eval: " "
-make: "cond-token-string.mk" line 54: The string literal " " is not empty, even though it consists of whitespace only.
+make: cond-token-string.mk:60: The string literal " " is not empty, even though it consists of whitespace only.
CondParser_Eval: "${UNDEF}"
-make: "cond-token-string.mk" line 63: An undefined variable in quotes expands to an empty string, which then evaluates to false.
+make: cond-token-string.mk:70: 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.
+make: cond-token-string.mk:76: A nonempty expression evaluates to true.
CondParser_Eval: "${:U}"
-make: "cond-token-string.mk" line 76: An empty variable evaluates to false.
+make: cond-token-string.mk:85: An empty variable evaluates to false.
+CondParser_Eval: ("${VAR}")
+CondParser_Eval: "quoted" == quoted
+Comparing "quoted" == "quoted"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-token-string.mk b/contrib/bmake/unit-tests/cond-token-string.mk
index a92d3a896116..9afe64dca821 100644
--- a/contrib/bmake/unit-tests/cond-token-string.mk
+++ b/contrib/bmake/unit-tests/cond-token-string.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-token-string.mk,v 1.4 2021/01/21 00:38:28 rillig Exp $
+# $NetBSD: cond-token-string.mk,v 1.17 2025/06/28 22:39:28 rillig Exp $
#
# Tests for quoted string literals in .if conditions.
#
@@ -9,7 +9,8 @@
# TODO: Implementation
# Cover the code in CondParser_String that frees the memory after parsing
-# a variable expression based on an undefined variable.
+# an expression based on an undefined variable.
+# expect+1: Unknown modifier ":Z"
.if "" != "${:Uvalue:Z}"
. error
.else
@@ -19,12 +20,14 @@
.if x${:Uvalue}
. error
.else
+# expect+1: xvalue is not defined.
. info xvalue is not defined.
.endif
# The 'x' produces a "Malformed conditional" since the left-hand side of a
-# comparison in an .if directive must be either a variable expression, a
+# comparison in an .if directive must be either an expression, a
# quoted string literal or a number that starts with a digit.
+# expect+1: Malformed conditional "x${:Uvalue} == """
.if x${:Uvalue} == ""
. error
.else
@@ -34,6 +37,7 @@
# In plain words, a '\' can be used to escape any character, just as in
# double-quoted string literals. See CondParser_String.
.if \x${:Uvalue} == "xvalue"
+# expect+1: Expected.
. info Expected.
.else
. error
@@ -43,6 +47,7 @@
# A string in quotes is checked whether it is not empty.
.if "UNDEF"
+# expect+1: The string literal "UNDEF" is not empty.
. info The string literal "UNDEF" is not empty.
.else
. error
@@ -51,6 +56,7 @@
# A space is not empty as well.
# This differs from many other places where whitespace is trimmed.
.if " "
+# expect+1: The string literal " " is not empty, even though it consists of whitespace only.
. info The string literal " " is not empty, even though it consists of $\
whitespace only.
.else
@@ -60,12 +66,14 @@
.if "${UNDEF}"
. error
.else
+# expect+1: An undefined variable in quotes expands to an empty string, which then evaluates to false.
. info An undefined variable in quotes expands to an empty string, which $\
then evaluates to false.
.endif
.if "${:Uvalue}"
-. info A nonempty variable expression evaluates to true.
+# expect+1: A nonempty expression evaluates to true.
+. info A nonempty expression evaluates to true.
.else
. error
.endif
@@ -73,10 +81,28 @@
.if "${:U}"
. error
.else
+# expect+1: An empty variable evaluates to false.
. info An empty variable evaluates to false.
.endif
+# A non-empty string evaluates to true, no matter if it's a literal string or
+# if it contains expressions. The parentheses are not necessary for
+# the parser, in this case their only purpose is to make the code harder to
+# read for humans.
+VAR= value
+.if ("${VAR}")
+.else
+. error
+.endif
+
+# In the conditions in .if directives, the left-hand side of a comparison must
+# be enclosed in quotes. The right-hand side does not need to be enclosed in
+# quotes.
+.if "quoted" == quoted
+.else
+. error
+.endif
+
.MAKEFLAGS: -d0
-all:
- @:;
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/cond-token-var.exp b/contrib/bmake/unit-tests/cond-token-var.exp
index fcd92d12a3da..b63d606c7e5a 100644
--- a/contrib/bmake/unit-tests/cond-token-var.exp
+++ b/contrib/bmake/unit-tests/cond-token-var.exp
@@ -1,7 +1,27 @@
-make: "cond-token-var.mk" line 20: ok
-make: "cond-token-var.mk" line 27: Malformed conditional (${UNDEF} == ${DEF})
-make: "cond-token-var.mk" line 33: Malformed conditional (${DEF} == ${UNDEF})
-make: "cond-token-var.mk" line 42: Malformed conditional (${UNDEF})
+make: cond-token-var.mk:23: ok
+make: cond-token-var.mk:30: Variable "UNDEF" is undefined
+make: cond-token-var.mk:36: Variable "UNDEF" is undefined
+make: cond-token-var.mk:46: Variable "UNDEF" is undefined
+make: cond-token-var.mk:64: Variable "U" is undefined
+make: cond-token-var.mk:69: Variable "U" is undefined
+make: cond-token-var.mk:78: Variable "U" is undefined
+Var_Parse: ${UNDEF1}y == "${UNDEF2}" || 0x${UNDEF3} (eval)
+make: cond-token-var.mk:106: Malformed conditional "x${UNDEF1}y == "${UNDEF2}" || 0x${UNDEF3}"
+Var_Parse: ${DEF}y == "${UNDEF2}" || 0x${UNDEF3} (eval)
+make: cond-token-var.mk:111: Malformed conditional "x${DEF}y == "${UNDEF2}" || 0x${UNDEF3}"
+Var_Parse: ${DEF}y == "${DEF}" || 0x${UNDEF3} (eval)
+make: cond-token-var.mk:116: Malformed conditional "x${DEF}y == "${DEF}" || 0x${UNDEF3}"
+Global: VAR.param = value of VAR.param
+Var_Parse: ${VAR.param$U} (eval-defined-loud)
+Var_Parse: $U} (eval)
+Global: .MAKEFLAGS = -r -k -d v -d
+Global: .MAKEFLAGS = -r -k -d v -d 0
+make: cond-token-var.mk:133: Variable "UNDEF" is undefined
+ while evaluating variable "UNDEF" with value ""
+make: cond-token-var.mk:142: Variable "UNDEF" is undefined
+ while evaluating variable "UNDEF" with value ""
+make: cond-token-var.mk:151: Variable "UNDEF" is undefined
+ while evaluating variable "UNDEF" with value ""
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-token-var.mk b/contrib/bmake/unit-tests/cond-token-var.mk
index 168c63c46ac1..842ca4d2cb12 100644
--- a/contrib/bmake/unit-tests/cond-token-var.mk
+++ b/contrib/bmake/unit-tests/cond-token-var.mk
@@ -1,11 +1,11 @@
-# $NetBSD: cond-token-var.mk,v 1.6 2021/04/25 21:05:38 rillig Exp $
+# $NetBSD: cond-token-var.mk,v 1.14 2025/06/28 22:39:28 rillig Exp $
#
-# Tests for variable expressions in .if conditions.
+# Tests for expressions in .if conditions.
#
-# Note the fine distinction between a variable and a variable expression.
-# A variable has a name and a value. To access the value, one writes a
-# variable expression of the form ${VAR}. This is a simple variable
-# expression. Variable expressions can get more complicated by adding
+# Note the fine distinction between a variable and an expression.
+# A variable has a name and a value. To access the value, one writes an
+# expression of the form ${VAR}. This is a simple
+# expression. Expressions can get more complicated by adding
# variable modifiers such as in ${VAR:Mpattern}.
#
# XXX: Strictly speaking, variable modifiers should be called expression
@@ -13,23 +13,26 @@
# Well, except for the assignment modifiers, these do indeed change the value
# of the variable.
+D= defined
DEF= defined
+
# A defined variable may appear on either side of the comparison.
.if ${DEF} == ${DEF}
+# expect+1: ok
. info ok
.else
. error
.endif
# A variable that appears on the left-hand side must be defined.
-# The following line thus generates a parse error.
+# expect+1: Variable "UNDEF" is undefined
.if ${UNDEF} == ${DEF}
. error
.endif
# A variable that appears on the right-hand side must be defined.
-# The following line thus generates a parse error.
+# expect+1: Variable "UNDEF" is undefined
.if ${DEF} == ${UNDEF}
. error
.endif
@@ -39,6 +42,7 @@ DEF= defined
.endif
# An undefined variable on its own generates a parse error.
+# expect+1: Variable "UNDEF" is undefined
.if ${UNDEF}
.endif
@@ -47,7 +51,35 @@ DEF= defined
.if ${UNDEF:U}
.endif
-# If the value of the variable expression is a number, it is compared against
+
+# The same as above, for single-letter variables without braces or
+# parentheses.
+
+# A defined variable may appear on either side of the comparison.
+.if $D == $D
+.endif
+
+# A variable on the left-hand side must be defined.
+# expect+1: Variable "U" is undefined
+.if $U == $D
+.endif
+
+# A variable on the right-hand side must be defined.
+# expect+1: Variable "U" is undefined
+.if $D == $U
+.endif
+
+# A defined variable may appear as an expression of its own.
+.if $D
+.endif
+
+# An undefined variable without a comparison operator generates a parse error.
+# expect+1: Variable "U" is undefined
+.if $U
+.endif
+
+
+# If the value of the expression is a number, it is compared against
# zero.
.if ${:U0}
. error
@@ -56,7 +88,7 @@ DEF= defined
. error
.endif
-# If the value of the variable expression is not a number, any non-empty
+# If the value of the expression is not a number, any non-empty
# value evaluates to true, even if there is only whitespace.
.if ${:U}
. error
@@ -67,3 +99,57 @@ DEF= defined
.if !${:Uanything}
. error
.endif
+
+.MAKEFLAGS: -dv
+# The left-hand side of a comparison must not be an unquoted word.
+# expect+1: Malformed conditional "x${UNDEF1}y == "${UNDEF2}" || 0x${UNDEF3}"
+.if x${UNDEF1}y == "${UNDEF2}" || 0x${UNDEF3}
+.endif
+
+# The left-hand side of a comparison must not be an unquoted word.
+# expect+1: Malformed conditional "x${DEF}y == "${UNDEF2}" || 0x${UNDEF3}"
+.if x${DEF}y == "${UNDEF2}" || 0x${UNDEF3}
+.endif
+
+# The left-hand side of a comparison must not be an unquoted word.
+# expect+1: Malformed conditional "x${DEF}y == "${DEF}" || 0x${UNDEF3}"
+.if x${DEF}y == "${DEF}" || 0x${UNDEF3}
+.endif
+
+# An expression in a condition must not be based on an undefined variable,
+# but undefined variables may occur in the variable name or in modifiers.
+#
+# expect: Var_Parse: ${VAR.param$U} (eval-defined-loud)
+# expect: Var_Parse: $U} (eval)
+VAR.param= value of VAR.param
+.if ${VAR.param$U}
+.endif
+
+.MAKEFLAGS: -d0
+
+
+# An expression in a comparison must not be undefined and have modifiers.
+# expect+1: Variable "UNDEF" is undefined
+.if ${UNDEF:M*}
+. error
+.else
+. error
+.endif
+
+# The left-hand side of a comparison must not be an undefined expression with
+# modifiers.
+# expect+1: Variable "UNDEF" is undefined
+.if ${UNDEF:M*} != ""
+. error
+.else
+. error
+.endif
+
+# The right-hand side of a comparison must not be an undefined expression with
+# modifiers.
+# expect+1: Variable "UNDEF" is undefined
+.if ${:U} != ${UNDEF:M*}
+. error
+.else
+. error
+.endif
diff --git a/contrib/bmake/unit-tests/cond-undef-lint.exp b/contrib/bmake/unit-tests/cond-undef-lint.exp
index 2c4feb0376ff..6ffefa6a0243 100755
--- a/contrib/bmake/unit-tests/cond-undef-lint.exp
+++ b/contrib/bmake/unit-tests/cond-undef-lint.exp
@@ -1,7 +1,6 @@
-make: "cond-undef-lint.mk" line 23: Variable "UNDEF" is undefined
-make: "cond-undef-lint.mk" line 38: Variable "UNDEF" is undefined
-make: "cond-undef-lint.mk" line 38: Variable "VAR." is undefined
-make: "cond-undef-lint.mk" line 49: Variable "VAR.defined" is undefined
+make: cond-undef-lint.mk:24: Variable "UNDEF" is undefined
+make: cond-undef-lint.mk:35: Variable "VAR." is undefined
+make: cond-undef-lint.mk:45: Variable "VAR.defined" is undefined
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-undef-lint.mk b/contrib/bmake/unit-tests/cond-undef-lint.mk
index 9dfd1bd53252..1b4d19636c41 100755
--- a/contrib/bmake/unit-tests/cond-undef-lint.mk
+++ b/contrib/bmake/unit-tests/cond-undef-lint.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-undef-lint.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $
+# $NetBSD: cond-undef-lint.mk,v 1.8 2025/01/11 21:21:33 rillig Exp $
#
# Tests for defined and undefined variables in .if conditions, in lint mode.
#
@@ -20,6 +20,7 @@ DEF= defined
.endif
# Since the condition fails to evaluate, neither of the branches is taken.
+# expect+1: Variable "UNDEF" is undefined
.if ${UNDEF}
. error
.else
@@ -30,22 +31,17 @@ DEF= defined
# mistake. The variable UNDEF, as used here, can be easily turned into
# an expression that is always defined, using the :U modifier.
#
-# The outer expression does not generate an error message since there was
-# already an error evaluating this variable's name.
-#
-# TODO: Suppress the error message "Variable VAR. is undefined". That part
-# of the expression must not be evaluated at all.
+# expect+1: Variable "VAR." is undefined
.if ${VAR.${UNDEF}}
. error
.else
. error
.endif
-# The variable VAR.defined is not defined and thus generates an error message.
+# The inner variable DEF is defined, but the resulting name VAR.defined
+# refers to an undefined variable, thus an error message.
#
-# TODO: This pattern looks a lot like CFLAGS.${OPSYS}, which is at least
-# debatable. Or would any practical use of CFLAGS.${OPSYS} be via an indirect
-# expression, as in the next example?
+# expect+1: Variable "VAR.defined" is undefined
.if ${VAR.${DEF}}
. error
.else
diff --git a/contrib/bmake/unit-tests/cond1.exp b/contrib/bmake/unit-tests/cond1.exp
deleted file mode 100644
index 8b65d782524d..000000000000
--- a/contrib/bmake/unit-tests/cond1.exp
+++ /dev/null
@@ -1,23 +0,0 @@
-make: "cond1.mk" line 80: warning: extra else
-make: "cond1.mk" line 90: warning: extra else
-2 is prime
-A='other' B='unknown' C='clever' o='no,no'
-Passed:
- var
- ("var")
- (var != var)
- var != var
- !((var != var) && defined(name))
- var == quoted
-
-1 is not prime
-2 is prime
-3 is prime
-4 is not prime
-5 is prime
-
-make: String comparison operator must be either == or !=
-make: Bad conditional expression '"0" > 0' in '"0" > 0?OK:No'
-
-OK
-exit status 0
diff --git a/contrib/bmake/unit-tests/cond1.mk b/contrib/bmake/unit-tests/cond1.mk
deleted file mode 100644
index 53908c2dacf1..000000000000
--- a/contrib/bmake/unit-tests/cond1.mk
+++ /dev/null
@@ -1,114 +0,0 @@
-# $NetBSD: cond1.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $
-
-# TODO: Convert these tests into tutorial form.
-# TODO: Split these tests by topic.
-# TODO: Use better variable names and expression values that actually express
-# the intended behavior. uname(1) has nothing to do with conditions.
-
-# hard code these!
-TEST_UNAME_S= NetBSD
-TEST_UNAME_M= sparc
-TEST_MACHINE= i386
-
-.if ${TEST_UNAME_S}
-Ok=var,
-.endif
-.if ("${TEST_UNAME_S}")
-Ok+=(\"var\"),
-.endif
-.if (${TEST_UNAME_M} != ${TEST_MACHINE})
-Ok+=(var != var),
-.endif
-.if ${TEST_UNAME_M} != ${TEST_MACHINE}
-Ok+= var != var,
-.endif
-.if !((${TEST_UNAME_M} != ${TEST_MACHINE}) && defined(X))
-Ok+= !((var != var) && defined(name)),
-.endif
-# from bsd.obj.mk
-MKOBJ?=no
-.if ${MKOBJ} == "no"
-o= no
-Ok+= var == "quoted",
-.else
-.if defined(notMAKEOBJDIRPREFIX) || defined(norMAKEOBJDIR)
-.if defined(notMAKEOBJDIRPREFIX)
-o=${MAKEOBJDIRPREFIX}${__curdir}
-.else
-o= ${MAKEOBJDIR}
-.endif
-.endif
-o= o
-.endif
-
-# repeat the above to check we get the same result
-.if ${MKOBJ} == "no"
-o2= no
-.else
-.if defined(notMAKEOBJDIRPREFIX) || defined(norMAKEOBJDIR)
-.if defined(notMAKEOBJDIRPREFIX)
-o2=${MAKEOBJDIRPREFIX}${__curdir}
-.else
-o2= ${MAKEOBJDIR}
-.endif
-.endif
-o2= o
-.endif
-
-PRIMES=2 3 5 7 11
-NUMBERS=1 2 3 4 5
-
-n=2
-.if ${PRIMES:M$n} == ""
-X=not
-.else
-X=
-.endif
-
-.if ${MACHINE_ARCH} == no-such
-A=one
-.else
-.if ${MACHINE_ARCH} == not-this
-.if ${MACHINE_ARCH} == something-else
-A=unlikely
-.else
-A=no
-.endif
-.endif
-A=other
-# We expect an extra else warning - we're not skipping here
-.else
-A=this should be an error
-.endif
-
-.if $X != ""
-.if $X == not
-B=one
-.else
-B=other
-# We expect an extra else warning - we are skipping here
-.else
-B=this should be an error
-.endif
-.else
-B=unknown
-.endif
-
-.if "quoted" == quoted
-C=clever
-.else
-C=dim
-.endif
-
-.if defined(nosuch) && ${nosuch:Mx} != ""
-# this should not happen
-.info nosuch is x
-.endif
-
-all:
- @echo "$n is $X prime"
- @echo "A='$A' B='$B' C='$C' o='$o,${o2}'"
- @echo "Passed:${.newline} ${Ok:S/,/${.newline}/}"
- @echo "${NUMBERS:@n@$n is ${("${PRIMES:M$n}" == ""):?not:} prime${.newline}@}"
- @echo "${"${DoNotQuoteHere:U0}" > 0:?OK:No}"
- @echo "${${NoSuchNumber:U42} > 0:?OK:No}"
diff --git a/contrib/bmake/unit-tests/dep-colon-bug-cross-file.exp b/contrib/bmake/unit-tests/dep-colon-bug-cross-file.exp
index 855b575c48bc..b3b5cd753b02 100644
--- a/contrib/bmake/unit-tests/dep-colon-bug-cross-file.exp
+++ b/contrib/bmake/unit-tests/dep-colon-bug-cross-file.exp
@@ -1,4 +1,4 @@
-make: "dep-colon-bug-cross-file.mk" line 31: warning: duplicate script for target "all" ignored
-make: "dep-colon-bug-cross-file.mk" line 40: warning: using previous script for "all" defined here
+make: dep-colon-bug-cross-file.mk:32: warning: duplicate script for target "all" ignored
+make: dep-colon-bug-cross-file.mk:42: warning: using previous script for "all" defined here
: pass 1
exit status 0
diff --git a/contrib/bmake/unit-tests/dep-colon-bug-cross-file.mk b/contrib/bmake/unit-tests/dep-colon-bug-cross-file.mk
index 57fbf478163c..930358af2871 100644
--- a/contrib/bmake/unit-tests/dep-colon-bug-cross-file.mk
+++ b/contrib/bmake/unit-tests/dep-colon-bug-cross-file.mk
@@ -1,4 +1,4 @@
-# $NetBSD: dep-colon-bug-cross-file.mk,v 1.4 2020/09/27 09:53:41 rillig Exp $
+# $NetBSD: dep-colon-bug-cross-file.mk,v 1.5 2023/06/01 20:56:35 rillig Exp $
#
# Until 2020-09-25, the very last dependency group of a top-level makefile
# was not finished properly. This made it possible to add further commands
@@ -28,6 +28,7 @@ PASS?= 1
.if ${PASS} == 2
all:
+# expect+1: warning: duplicate script for target "all" ignored
: pass 2
.endif
@@ -37,5 +38,6 @@ PASS= 2
.MAKEFLAGS: -f ${.PARSEDIR:q}/${.PARSEFILE:q}
all:
+# expect+1: warning: using previous script for "all" defined here
: pass 1
.endif
diff --git a/contrib/bmake/unit-tests/dep-duplicate.exp b/contrib/bmake/unit-tests/dep-duplicate.exp
index 039145f8fd97..3eaa8a4ce5d1 100644
--- a/contrib/bmake/unit-tests/dep-duplicate.exp
+++ b/contrib/bmake/unit-tests/dep-duplicate.exp
@@ -1,4 +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
+make: dep-duplicate.tmp:1: warning: duplicate script for target "all" ignored
+make: dep-duplicate.main: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
index 6f64ba1c1981..8b05b9fb062d 100644
--- a/contrib/bmake/unit-tests/dep-duplicate.mk
+++ b/contrib/bmake/unit-tests/dep-duplicate.mk
@@ -1,4 +1,4 @@
-# $NetBSD: dep-duplicate.mk,v 1.3 2022/01/20 19:24:53 rillig Exp $
+# $NetBSD: dep-duplicate.mk,v 1.4 2024/05/25 21:11:30 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.
@@ -13,9 +13,9 @@ all: .PHONY
echo '# empty line 1'; \
echo '# empty line 2'; \
echo 'all:; @echo main-output'; \
- echo '.include "dep-duplicate.inc"'
+ echo '.include "dep-duplicate.tmp"'
- @exec > dep-duplicate.inc; \
+ @exec > dep-duplicate.tmp; \
echo 'all:; @echo inc-output'
# The main file must be specified using a relative path, just like the
@@ -24,4 +24,4 @@ all: .PHONY
@${MAKE} -r -f dep-duplicate.main
@rm -f dep-duplicate.main
- @rm -f dep-duplicate.inc
+ @rm -f dep-duplicate.tmp
diff --git a/contrib/bmake/unit-tests/dep-op-missing.exp b/contrib/bmake/unit-tests/dep-op-missing.exp
index 58b9be353eaa..7c03092e09be 100644
--- a/contrib/bmake/unit-tests/dep-op-missing.exp
+++ b/contrib/bmake/unit-tests/dep-op-missing.exp
@@ -1,4 +1,5 @@
-make: "dep-op-missing.tmp" line 1: Invalid line type
+make: dep-op-missing.tmp:1: Invalid line "target"
+ in make[1] in directory "<curdir>"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 0
diff --git a/contrib/bmake/unit-tests/dep-percent.exp b/contrib/bmake/unit-tests/dep-percent.exp
index 1e6c04d2e167..fd3748d42d0f 100644
--- a/contrib/bmake/unit-tests/dep-percent.exp
+++ b/contrib/bmake/unit-tests/dep-percent.exp
@@ -2,5 +2,5 @@ make: don't know how to make dep-percent.o (continuing)
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/dep-var.exp b/contrib/bmake/unit-tests/dep-var.exp
index d32aca455ceb..4a570206b300 100755
--- a/contrib/bmake/unit-tests/dep-var.exp
+++ b/contrib/bmake/unit-tests/dep-var.exp
@@ -1,6 +1,30 @@
-make: Malformed variable expression at "$)"
+Var_Parse: ${UNDEF1} (eval)
+Global: .ALLTARGETS = all
+Global: .ALLTARGETS = all ${DEF2}
+Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b
+Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3}
+Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1
+Global: INDIRECT_1 = 2-$${INDIRECT_2}-2
+Global: INDIRECT_2 = 3-$${INDIRECT_3}-3
+Global: INDIRECT_3 = indirect
+Global: UNDEF1 = undef1
+Global: DEF2 = def2
+Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$)
+Var_Parse: ${:U\$)}: (eval)
+Evaluating modifier ${:U...} on value "" (eval, undefined)
+Result of ${:U\$)} is "$)" (eval, defined)
+Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1
+Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2
+Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2 a-def2-b
+Var_Parse: $INDIRECT_2-2-1 $): (parse)
+Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2 a-def2-b 1-2-$INDIRECT_2-2-1
+Var_Parse: $): (parse)
+Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2 a-def2-b 1-2-$INDIRECT_2-2-1 $)
+Global: .MAKEFLAGS = -r -k -d v -d
+Global: .MAKEFLAGS = -r -k -d v -d 0
+make: Malformed expression at "$)"
def2
a-def2-b
1-2-NDIRECT_2-2-1
)
-exit status 0
+exit status 2
diff --git a/contrib/bmake/unit-tests/dep-var.mk b/contrib/bmake/unit-tests/dep-var.mk
index 4503424e31ab..ed8549583b82 100755
--- a/contrib/bmake/unit-tests/dep-var.mk
+++ b/contrib/bmake/unit-tests/dep-var.mk
@@ -1,17 +1,17 @@
-# $NetBSD: dep-var.mk,v 1.6 2021/04/04 10:13:09 rillig Exp $
+# $NetBSD: dep-var.mk,v 1.13 2025/01/14 21:23:17 rillig Exp $
#
# Tests for variable references in dependency declarations.
#
# Uh oh, this feels so strange that probably nobody uses it. But it seems to
# be the only way to reach the lower half of SuffExpandChildren.
-# XXX: The -dv log says:
-# Var_Parse: ${UNDEF1} with VARE_UNDEFERR|VARE_WANTRES
-# but no error message is generated for this line.
-# The variable expression ${UNDEF1} simply expands to an empty string.
+.MAKEFLAGS: -dv
+
+# In a dependency line, an undefined expressions expands to an empty string.
+# expect: Var_Parse: ${UNDEF1} (eval)
all: ${UNDEF1}
-# Using a double dollar in order to circumvent immediate variable expansion
+# Using a double dollar in order to circumvent immediate expression expansion
# feels like unintended behavior. At least the manual page says nothing at
# all about defined or undefined variables in dependency lines.
#
@@ -19,11 +19,7 @@ all: ${UNDEF1}
# is defined, so everything's fine.
all: $${DEF2} a-$${DEF2}-b
-# This variable is not defined at all.
-# XXX: The -dv log says:
-# Var_Parse: ${UNDEF3} with VARE_UNDEFERR|VARE_WANTRES
-# but no error message is generated for this line, just like for UNDEF1.
-# The variable expression ${UNDEF3} simply expands to an empty string.
+# This variable is neither defined now nor later.
all: $${UNDEF3}
# Try out how many levels of indirection are really expanded in dependency
@@ -61,7 +57,7 @@ INDIRECT_3= indirect
UNDEF1= undef1
DEF2= def2
-# Cover the code in SuffExpandChildren that deals with malformed variable
+# Cover the code in SuffExpandChildren that deals with malformed
# expressions.
#
# This seems to be an edge case that never happens in practice, and it would
@@ -81,8 +77,10 @@ all: $$$$)
# Since 2020-09-13, this generates a parse error in lint mode (-dL), but not
# in normal mode since ParseDependency does not handle any errors after
# calling Var_Parse.
+# expect: Var_Parse: ${:U\$)}: (eval)
+# expect: Var_Parse: $INDIRECT_2-2-1 $): (parse)
+# expect: Var_Parse: $): (parse)
undef1 def2 a-def2-b 1-2-$$INDIRECT_2-2-1 ${:U\$)}:
@echo ${.TARGET:Q}
-# XXX: Why is the exit status still 0, even though Parse_Error is called
-# with PARSE_FATAL in SuffExpandChildren?
+.MAKEFLAGS: -d0
diff --git a/contrib/bmake/unit-tests/dep-wildcards.mk b/contrib/bmake/unit-tests/dep-wildcards.mk
index 781b149f5a70..b3e2f14a07c3 100644
--- a/contrib/bmake/unit-tests/dep-wildcards.mk
+++ b/contrib/bmake/unit-tests/dep-wildcards.mk
@@ -1,4 +1,4 @@
-# $NetBSD: dep-wildcards.mk,v 1.3 2020/09/08 05:33:05 rillig Exp $
+# $NetBSD: dep-wildcards.mk,v 1.4 2023/06/21 12:27:50 rillig Exp $
#
# Tests for wildcards such as *.c in dependency declarations.
@@ -7,3 +7,9 @@ all: ${.PARSEDIR}/dep-*.mk
# The :O is necessary since the result of the dependency resolution
# does not order the directory entries itself.
@printf '%s\n' ${.ALLSRC:T:O}
+
+# This is not a wildcard rule as implemented by GNU make, as those rules would
+# use '%' instead of '*'. Instead, the pattern '*.target' is a file pattern
+# in the current working directory. As there are no such files, the target
+# list becomes empty, and the source pattern '*.source' is not even expanded.
+*.target: *.source
diff --git a/contrib/bmake/unit-tests/dep.exp b/contrib/bmake/unit-tests/dep.exp
index 6b7f0fabb12b..c08f86bc73db 100644
--- a/contrib/bmake/unit-tests/dep.exp
+++ b/contrib/bmake/unit-tests/dep.exp
@@ -1,5 +1,5 @@
-make: "dep.mk" line 11: Inconsistent operator for only-colon
-make: "dep.mk" line 13: Inconsistent operator for only-colon
+make: dep.mk:11: Inconsistent operator for only-colon
+make: dep.mk:13: Inconsistent operator for only-colon
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/dep.mk b/contrib/bmake/unit-tests/dep.mk
index 54566d43d2a1..53fadc789b13 100644
--- a/contrib/bmake/unit-tests/dep.mk
+++ b/contrib/bmake/unit-tests/dep.mk
@@ -1,4 +1,4 @@
-# $NetBSD: dep.mk,v 1.3 2021/12/13 23:38:54 rillig Exp $
+# $NetBSD: dep.mk,v 1.4 2023/06/01 07:27:30 rillig Exp $
#
# Tests for dependency declarations, such as "target: sources".
@@ -15,4 +15,16 @@ only-colon::
# would be another error message.
only-colon:
+
+# Before parse.c 1.158 from 2009-10-07, the parser broke dependency lines at
+# the first ';', without parsing expressions as such. It interpreted the
+# first ';' as the separator between the dependency and its commands, and the
+# '^' as a shell command.
+all: for-subst
+.for file in ${.PARSEFILE}
+for-subst: ${file:S;^;./;g}
+ @echo ".for with :S;... OK"
+.endfor
+
+
all:
diff --git a/contrib/bmake/unit-tests/depsrc-end.mk b/contrib/bmake/unit-tests/depsrc-end.mk
index eb7543d5dfad..1bfe50d98620 100644
--- a/contrib/bmake/unit-tests/depsrc-end.mk
+++ b/contrib/bmake/unit-tests/depsrc-end.mk
@@ -1,6 +1,6 @@
-# $NetBSD: depsrc-end.mk,v 1.1 2020/10/23 19:23:01 rillig Exp $
+# $NetBSD: depsrc-end.mk,v 1.2 2024/04/27 20:41:32 rillig Exp $
#
-# Demonstrate the edge case that .BEGIN depends on .END, which sounds a bit
+# Demonstrate an edge case in which .BEGIN depends on .END, which sounds a bit
# paradox but works since these special nodes are not in the dependency
# hierarchy where the cycles are detected.
diff --git a/contrib/bmake/unit-tests/depsrc-ignore.exp b/contrib/bmake/unit-tests/depsrc-ignore.exp
index 162f10ddc17b..1fc45d3d7239 100644
--- a/contrib/bmake/unit-tests/depsrc-ignore.exp
+++ b/contrib/bmake/unit-tests/depsrc-ignore.exp
@@ -1,11 +1,11 @@
ignore-errors begin
false ignore-errors
+*** Error code 1 (ignored)
ignore-errors end
all begin
-*** Error code 1 (ignored)
false all
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/depsrc-nopath.exp b/contrib/bmake/unit-tests/depsrc-nopath.exp
index 39a9383953dd..01295d8df7f5 100644
--- a/contrib/bmake/unit-tests/depsrc-nopath.exp
+++ b/contrib/bmake/unit-tests/depsrc-nopath.exp
@@ -1 +1,3 @@
+: Making test-regular from depsrc-nopath.dir/regular.file
+: Making test-nopath from nopath.file
exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-nopath.mk b/contrib/bmake/unit-tests/depsrc-nopath.mk
index 052c6f10db66..8d9ce93c16b9 100644
--- a/contrib/bmake/unit-tests/depsrc-nopath.mk
+++ b/contrib/bmake/unit-tests/depsrc-nopath.mk
@@ -1,8 +1,27 @@
-# $NetBSD: depsrc-nopath.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: depsrc-nopath.mk,v 1.3 2024/04/27 20:41:32 rillig Exp $
#
# Tests for the special source .NOPATH in dependency declarations.
-# TODO: Implementation
+.if !target(test-*)
+_!= rm -rf depsrc-nopath.dir
+_!= mkdir depsrc-nopath.dir
+_!= touch depsrc-nopath.dir/regular.file
+_!= touch depsrc-nopath.dir/nopath.file
+.endif
all:
- @:;
+ @${MAKE} -f ${MAKEFILE} test-regular
+ @${MAKE} -f ${MAKEFILE} test-nopath || echo "should have failed"
+ @rm -rf depsrc-nopath.dir
+
+.PATH: depsrc-nopath.dir
+
+test-regular: regular.file
+ : Making ${.TARGET} from ${.ALLSRC}
+test-nopath: nopath.file
+ : Making ${.TARGET} from ${.ALLSRC}
+
+nopath.file: .NOPATH
+
+# expect: : Making test-regular from depsrc-nopath.dir/regular.file
+# expect: : Making test-nopath from nopath.file
diff --git a/contrib/bmake/unit-tests/depsrc-phony.mk b/contrib/bmake/unit-tests/depsrc-phony.mk
index c41efac369a8..9df1eb570ab4 100644
--- a/contrib/bmake/unit-tests/depsrc-phony.mk
+++ b/contrib/bmake/unit-tests/depsrc-phony.mk
@@ -1,9 +1,10 @@
-# $NetBSD: depsrc-phony.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $
+# $NetBSD: depsrc-phony.mk,v 1.4 2024/04/27 20:41:32 rillig Exp $
#
# Tests for the special source .PHONY in dependency declarations,
# which executes the commands for the target even if a file of the same
# name exists and would be considered up to date.
# Without the .PHONY, this target would be "up to date".
+# expect: : depsrc-phony.mk is made.
${MAKEFILE}: .PHONY
: ${.TARGET:T} is made.
diff --git a/contrib/bmake/unit-tests/depsrc-wait.exp b/contrib/bmake/unit-tests/depsrc-wait.exp
index d1a60fbaa6e6..36bcb0678151 100644
--- a/contrib/bmake/unit-tests/depsrc-wait.exp
+++ b/contrib/bmake/unit-tests/depsrc-wait.exp
@@ -1,13 +1,18 @@
---- a ---
echo a
a
---- b1 ---
echo b1
b1
---- b ---
echo b
b
---- x ---
echo x
x
+: Making 3a
+: Making 3a
+: Making 3a
+: Making 3b
+: Making 3b
+: Making 3b
+: Making 3c
+: Making 3c
+: Making 3c
exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-wait.mk b/contrib/bmake/unit-tests/depsrc-wait.mk
index 95b0ea96e0a5..ab974d47c29d 100644
--- a/contrib/bmake/unit-tests/depsrc-wait.mk
+++ b/contrib/bmake/unit-tests/depsrc-wait.mk
@@ -1,9 +1,15 @@
-# $NetBSD: depsrc-wait.mk,v 1.3 2020/09/07 18:40:32 rillig Exp $
+# $NetBSD: depsrc-wait.mk,v 1.4 2022/05/07 17:49:47 rillig Exp $
#
# Tests for the special source .WAIT in dependency declarations,
# which adds a sequence point between the nodes to its left and the nodes
# to its right.
+all: .PHONY
+ @${MAKE} -r -f ${MAKEFILE} x
+ @${MAKE} -r -f ${MAKEFILE} three-by-three
+
+
+.if make(x)
# Even though the build could run massively parallel, the .WAIT imposes a
# strict ordering in this example, which forces the targets to be made in
# exactly this order.
@@ -19,3 +25,17 @@ b: b1
echo b
b1:
echo b1
+.endif
+
+
+# There are 3 groups of 3 targets, with .WAIT barriers in between. Each of
+# these groups has to be made completely before starting the next group.
+# See Makefile, POSTPROC for the postprocessing that takes place.
+.if make(three-by-three)
+.MAKEFLAGS: -j5
+.MAKE.MODE+= randomize-targets
+
+three-by-three: .WAIT 3a1 3a2 3a3 .WAIT 3b1 3b2 3b3 .WAIT 3c1 3c2 3c3 .WAIT
+3a1 3a2 3a3 3b1 3b2 3b3 3c1 3c2 3c3:
+ : Making ${.TARGET}
+.endif
diff --git a/contrib/bmake/unit-tests/deptgt-begin-fail-indirect.exp b/contrib/bmake/unit-tests/deptgt-begin-fail-indirect.exp
index 59575e839a4a..46ae9ff95ff9 100644
--- a/contrib/bmake/unit-tests/deptgt-begin-fail-indirect.exp
+++ b/contrib/bmake/unit-tests/deptgt-begin-fail-indirect.exp
@@ -2,5 +2,5 @@ false
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/deptgt-begin-fail.exp b/contrib/bmake/unit-tests/deptgt-begin-fail.exp
index 59575e839a4a..46ae9ff95ff9 100644
--- a/contrib/bmake/unit-tests/deptgt-begin-fail.exp
+++ b/contrib/bmake/unit-tests/deptgt-begin-fail.exp
@@ -2,5 +2,5 @@ false
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/deptgt-begin.exp b/contrib/bmake/unit-tests/deptgt-begin.exp
index abc80afe9964..5aa673e33d30 100644
--- a/contrib/bmake/unit-tests/deptgt-begin.exp
+++ b/contrib/bmake/unit-tests/deptgt-begin.exp
@@ -1,5 +1,5 @@
-make: "deptgt-begin.mk" line 17: warning: duplicate script for target ".BEGIN" ignored
-make: "deptgt-begin.mk" line 8: warning: using previous script for ".BEGIN" defined here
+make: deptgt-begin.mk:19: warning: duplicate script for target ".BEGIN" ignored
+make: deptgt-begin.mk:8: warning: using previous script for ".BEGIN" defined here
: parse time
: Making before-begin before .BEGIN.
: .BEGIN
diff --git a/contrib/bmake/unit-tests/deptgt-begin.mk b/contrib/bmake/unit-tests/deptgt-begin.mk
index b71d78f371ed..a29155cb5fc2 100644
--- a/contrib/bmake/unit-tests/deptgt-begin.mk
+++ b/contrib/bmake/unit-tests/deptgt-begin.mk
@@ -1,4 +1,4 @@
-# $NetBSD: deptgt-begin.mk,v 1.5 2020/11/15 22:28:08 rillig Exp $
+# $NetBSD: deptgt-begin.mk,v 1.8 2025/06/30 21:44:39 rillig Exp $
#
# Tests for the special target .BEGIN in dependency declarations,
# which is a container for commands that are run before any other
@@ -13,6 +13,8 @@
# add its commands after this.
#
# There are several ways to resolve this situation, which are detailed below.
+# expect+3: warning: duplicate script for target ".BEGIN" ignored
+# expect-9: warning: using previous script for ".BEGIN" defined here
.BEGIN:
: Making another $@.
@@ -25,8 +27,8 @@ before-begin: .PHONY .NOTMAIN
# Another way is to define a custom target and make that a .USE dependency.
# For the .BEGIN target, .USE dependencies do not work though, since in
-# Compat_Run, the .USE and .USEBEFORE nodes are expanded right after the
-# .BEGIN target has been run, which is too late.
+# Compat_MakeAll, the .USE and .USEBEFORE nodes are expanded right after the
+# .BEGIN target has been made, which is too late.
.BEGIN: use
use: .USE .NOTMAIN
: Making $@ from a .USE dependency.
@@ -35,8 +37,8 @@ use: .USE .NOTMAIN
# .BEGIN target.
#
# For the .BEGIN target, .USEBEFORE dependencies do not work though, since in
-# Compat_Run, the .USE and .USEBEFORE nodes are expanded right after the
-# .BEGIN target has been run, which is too late.
+# Compat_MakeAll, the .USE and .USEBEFORE nodes are expanded right after the
+# .BEGIN target has been made, which is too late.
.BEGIN: use-before
use-before: .USEBEFORE .NOTMAIN
: Making $@ from a .USEBEFORE dependency.
diff --git a/contrib/bmake/unit-tests/deptgt-delete_on_error.exp b/contrib/bmake/unit-tests/deptgt-delete_on_error.exp
index 9d9f1dc3e5ec..9171c9931972 100644
--- a/contrib/bmake/unit-tests/deptgt-delete_on_error.exp
+++ b/contrib/bmake/unit-tests/deptgt-delete_on_error.exp
@@ -7,45 +7,43 @@ make: *** deptgt-delete_on_error-regular removed
make: *** deptgt-delete_on_error-regular-delete removed
> deptgt-delete_on_error-phony; false
*** Error code 1 (continuing)
-make: *** deptgt-delete_on_error-phony removed
> deptgt-delete_on_error-phony-delete; false
*** Error code 1 (continuing)
-make: *** deptgt-delete_on_error-phony-delete removed
> deptgt-delete_on_error-precious; false
*** Error code 1 (continuing)
> deptgt-delete_on_error-precious-delete; false
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "deptgt-delete_on_error-regular deptgt-delete_on_error-regular-delete deptgt-delete_on_error-phony deptgt-delete_on_error-phony-delete deptgt-delete_on_error-precious deptgt-delete_on_error-precious-delete" in unit-tests
+*** Error code 1 (ignored)
Parallel mode
> deptgt-delete_on_error-regular; false
*** [deptgt-delete_on_error-regular] Error code 1
make: *** deptgt-delete_on_error-regular removed
-make: stopped in unit-tests
+make: stopped making "deptgt-delete_on_error-regular deptgt-delete_on_error-regular-delete deptgt-delete_on_error-phony deptgt-delete_on_error-phony-delete deptgt-delete_on_error-precious deptgt-delete_on_error-precious-delete" in unit-tests
> deptgt-delete_on_error-regular-delete; false
*** [deptgt-delete_on_error-regular-delete] Error code 1
make: *** deptgt-delete_on_error-regular-delete removed
-make: stopped in unit-tests
+make: stopped making "deptgt-delete_on_error-regular deptgt-delete_on_error-regular-delete deptgt-delete_on_error-phony deptgt-delete_on_error-phony-delete deptgt-delete_on_error-precious deptgt-delete_on_error-precious-delete" in unit-tests
> deptgt-delete_on_error-phony; false
*** [deptgt-delete_on_error-phony] Error code 1
-make: stopped in unit-tests
+make: stopped making "deptgt-delete_on_error-regular deptgt-delete_on_error-regular-delete deptgt-delete_on_error-phony deptgt-delete_on_error-phony-delete deptgt-delete_on_error-precious deptgt-delete_on_error-precious-delete" in unit-tests
> deptgt-delete_on_error-phony-delete; false
*** [deptgt-delete_on_error-phony-delete] Error code 1
-make: stopped in unit-tests
+make: stopped making "deptgt-delete_on_error-regular deptgt-delete_on_error-regular-delete deptgt-delete_on_error-phony deptgt-delete_on_error-phony-delete deptgt-delete_on_error-precious deptgt-delete_on_error-precious-delete" in unit-tests
> deptgt-delete_on_error-precious; false
*** [deptgt-delete_on_error-precious] Error code 1
-make: stopped in unit-tests
+make: stopped making "deptgt-delete_on_error-regular deptgt-delete_on_error-regular-delete deptgt-delete_on_error-phony deptgt-delete_on_error-phony-delete deptgt-delete_on_error-precious deptgt-delete_on_error-precious-delete" in unit-tests
> deptgt-delete_on_error-precious-delete; false
*** [deptgt-delete_on_error-precious-delete] Error code 1
-make: stopped in unit-tests
-*** Error code 1 (ignored)
+make: stopped making "deptgt-delete_on_error-regular deptgt-delete_on_error-regular-delete deptgt-delete_on_error-phony deptgt-delete_on_error-phony-delete deptgt-delete_on_error-precious deptgt-delete_on_error-precious-delete" in unit-tests
*** Error code 1 (ignored)
exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-delete_on_error.mk b/contrib/bmake/unit-tests/deptgt-delete_on_error.mk
index e6d0610f4672..2309e67c01a5 100644
--- a/contrib/bmake/unit-tests/deptgt-delete_on_error.mk
+++ b/contrib/bmake/unit-tests/deptgt-delete_on_error.mk
@@ -1,4 +1,4 @@
- # $NetBSD: deptgt-delete_on_error.mk,v 1.3 2020/10/25 21:31:00 rillig Exp $
+# $NetBSD: deptgt-delete_on_error.mk,v 1.4 2023/06/01 20:56:35 rillig Exp $
#
# Tests for the special target .DELETE_ON_ERROR in dependency declarations,
# which controls whether the target is deleted if a shell command fails or
diff --git a/contrib/bmake/unit-tests/deptgt-end-fail-all.exp b/contrib/bmake/unit-tests/deptgt-end-fail-all.exp
index 2e2ee11f481a..39209f9709ce 100644
--- a/contrib/bmake/unit-tests/deptgt-end-fail-all.exp
+++ b/contrib/bmake/unit-tests/deptgt-end-fail-all.exp
@@ -3,5 +3,5 @@ false
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/deptgt-end-fail-indirect.exp b/contrib/bmake/unit-tests/deptgt-end-fail-indirect.exp
index 17e509600617..e0733527e59d 100644
--- a/contrib/bmake/unit-tests/deptgt-end-fail-indirect.exp
+++ b/contrib/bmake/unit-tests/deptgt-end-fail-indirect.exp
@@ -3,5 +3,5 @@ false
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/deptgt-end-fail-indirect.mk b/contrib/bmake/unit-tests/deptgt-end-fail-indirect.mk
index 29346b8321fe..dc921bcfda2c 100644
--- a/contrib/bmake/unit-tests/deptgt-end-fail-indirect.mk
+++ b/contrib/bmake/unit-tests/deptgt-end-fail-indirect.mk
@@ -1,10 +1,10 @@
-# $NetBSD: deptgt-end-fail-indirect.mk,v 1.2 2020/12/06 21:22:04 rillig Exp $
+# $NetBSD: deptgt-end-fail-indirect.mk,v 1.3 2022/05/07 08:01:20 rillig Exp $
#
# Tests for an error in a dependency of the .END node.
#
# Before 2020-11-25, an error in the .END target did not print the "Stop."
# and exited with status 0. The cause for this was a missing condition in
-# Compat_Run in the handling of the .END node.
+# Compat_MakeAll in the handling of the .END node.
all:
: $@
diff --git a/contrib/bmake/unit-tests/deptgt-end-fail.exp b/contrib/bmake/unit-tests/deptgt-end-fail.exp
index 9db907c209d5..8f4e22059829 100644
--- a/contrib/bmake/unit-tests/deptgt-end-fail.exp
+++ b/contrib/bmake/unit-tests/deptgt-end-fail.exp
@@ -13,7 +13,7 @@ Test case all=ok all-dep=ok end=ok end-dep=ERR.
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
@@ -25,7 +25,7 @@ Test case all=ok all-dep=ok end=ERR end-dep=ok.
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
@@ -36,7 +36,7 @@ Test case all=ok all-dep=ok end=ERR end-dep=ERR.
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
@@ -46,7 +46,7 @@ Test case all=ok all-dep=ERR end=ok end-dep=ok.
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
@@ -56,7 +56,7 @@ Test case all=ok all-dep=ERR end=ok end-dep=ERR.
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
@@ -66,7 +66,7 @@ Test case all=ok all-dep=ERR end=ERR end-dep=ok.
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
@@ -76,7 +76,7 @@ Test case all=ok all-dep=ERR end=ERR end-dep=ERR.
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
@@ -86,7 +86,7 @@ Test case all=ERR all-dep=ok end=ok end-dep=ok.
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
@@ -96,7 +96,7 @@ Test case all=ERR all-dep=ok end=ok end-dep=ERR.
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
@@ -106,7 +106,7 @@ Test case all=ERR all-dep=ok end=ERR end-dep=ok.
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
@@ -116,7 +116,7 @@ Test case all=ERR all-dep=ok end=ERR end-dep=ERR.
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
@@ -126,7 +126,7 @@ Test case all=ERR all-dep=ERR end=ok end-dep=ok.
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
@@ -136,7 +136,7 @@ Test case all=ERR all-dep=ERR end=ok end-dep=ERR.
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
@@ -146,7 +146,7 @@ Test case all=ERR all-dep=ERR end=ERR end-dep=ok.
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
@@ -156,7 +156,7 @@ Test case all=ERR all-dep=ERR end=ERR end-dep=ERR.
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/deptgt-end-fail.mk b/contrib/bmake/unit-tests/deptgt-end-fail.mk
index 57cdc7da8679..c77127f26091 100644
--- a/contrib/bmake/unit-tests/deptgt-end-fail.mk
+++ b/contrib/bmake/unit-tests/deptgt-end-fail.mk
@@ -1,11 +1,11 @@
-# $NetBSD: deptgt-end-fail.mk,v 1.6 2020/12/07 01:04:07 rillig Exp $
+# $NetBSD: deptgt-end-fail.mk,v 1.7 2022/05/07 08:01:20 rillig Exp $
#
# Tests for an errors in the main target, its dependencies,
# the .END node and its dependencies.
#
# Before 2020-11-25, an error in the .END target did not print the "Stop.",
# even though this was intended. The cause for this was a missing condition
-# in Compat_Run, in the code handling the .END node.
+# in Compat_MakeAll, in the code handling the .END node.
test: .PHONY
diff --git a/contrib/bmake/unit-tests/deptgt-error.exp b/contrib/bmake/unit-tests/deptgt-error.exp
index 48e2f90954cf..cc518aaa1b84 100644
--- a/contrib/bmake/unit-tests/deptgt-error.exp
+++ b/contrib/bmake/unit-tests/deptgt-error.exp
@@ -2,7 +2,7 @@ false fails
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
ERROR_INFO='This information is printed on 'errors'.'
Making sub-error as prerequisite.
Making .ERROR out of nothing.
diff --git a/contrib/bmake/unit-tests/deptgt-ignore.exp b/contrib/bmake/unit-tests/deptgt-ignore.exp
index 2aa1311c8ff7..8679b83af0d5 100644
--- a/contrib/bmake/unit-tests/deptgt-ignore.exp
+++ b/contrib/bmake/unit-tests/deptgt-ignore.exp
@@ -7,5 +7,5 @@ Making depends-on-ignored from error-ignored.
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/deptgt-makeflags.exp b/contrib/bmake/unit-tests/deptgt-makeflags.exp
index ac8ffc83470d..099ce8e15ed5 100644
--- a/contrib/bmake/unit-tests/deptgt-makeflags.exp
+++ b/contrib/bmake/unit-tests/deptgt-makeflags.exp
@@ -1,8 +1,8 @@
-Global: delete DOLLAR (not found)
+Global: ignoring delete 'DOLLAR' as it is not found
Command: DOLLAR = $$$$
Global: .MAKEOVERRIDES = VAR DOLLAR
CondParser_Eval: ${DOLLAR} != "\$\$"
-Var_Parse: ${DOLLAR} != "\$\$" (eval-defined)
+Var_Parse: ${DOLLAR} != "\$\$" (eval-defined-loud)
Comparing "$$" != "$$"
Global: .MAKEFLAGS = -r -k -D VAR -D VAR -d cv -d
Global: .MAKEFLAGS = -r -k -D VAR -D VAR -d cv -d 0
diff --git a/contrib/bmake/unit-tests/deptgt-makeflags.mk b/contrib/bmake/unit-tests/deptgt-makeflags.mk
index 26f3f5794354..2f8b00743e3f 100644
--- a/contrib/bmake/unit-tests/deptgt-makeflags.mk
+++ b/contrib/bmake/unit-tests/deptgt-makeflags.mk
@@ -1,4 +1,4 @@
-# $NetBSD: deptgt-makeflags.mk,v 1.7 2021/11/29 00:17:10 rillig Exp $
+# $NetBSD: deptgt-makeflags.mk,v 1.9 2023/11/19 22:32:44 rillig Exp $
#
# Tests for the special target .MAKEFLAGS in dependency declarations,
# which adds command line options later, at parse time.
@@ -65,9 +65,9 @@
.endif
# Next try at defining another newline variable. Since whitespace around the
-# variable value is trimmed, two empty variable expressions ${:U} surround the
+# variable value is trimmed, two empty expressions ${:U} surround the
# literal newline now. This prevents the newline from being skipped during
-# parsing. The ':=' assignment operator expands the empty variable
+# parsing. The ':=' assignment operator expands the empty
# expressions, leaving only the newline as the variable value.
#
# This is one of the very few ways (maybe even the only one) to inject literal
diff --git a/contrib/bmake/unit-tests/deptgt-order.exp b/contrib/bmake/unit-tests/deptgt-order.exp
index ecbf03fcc572..c5cdaa4cbe2b 100644
--- a/contrib/bmake/unit-tests/deptgt-order.exp
+++ b/contrib/bmake/unit-tests/deptgt-order.exp
@@ -1,9 +1,9 @@
-Parsing line 15: .ORDER: three one
+Parsing deptgt-order.mk: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
+Parsing deptgt-order.mk:16: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
: 'Making two out of one.'
: 'Making three out of two.'
diff --git a/contrib/bmake/unit-tests/deptgt-path-suffix.exp b/contrib/bmake/unit-tests/deptgt-path-suffix.exp
index 228a29851f48..e1c67daa8787 100644
--- a/contrib/bmake/unit-tests/deptgt-path-suffix.exp
+++ b/contrib/bmake/unit-tests/deptgt-path-suffix.exp
@@ -1,4 +1,4 @@
-make: "deptgt-path-suffix.mk" line 8: Suffix '.c' not defined (yet)
+make: deptgt-path-suffix.mk:8: Suffix ".c" not defined (yet)
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" 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 494a076a5520..68bb27036b68 100644
--- a/contrib/bmake/unit-tests/deptgt-path-suffix.mk
+++ b/contrib/bmake/unit-tests/deptgt-path-suffix.mk
@@ -1,10 +1,10 @@
-# $NetBSD: deptgt-path-suffix.mk,v 1.3 2021/12/13 23:38:54 rillig Exp $
+# $NetBSD: deptgt-path-suffix.mk,v 1.4 2025/06/28 22:39:28 rillig Exp $
#
# Tests for the special target .PATH.suffix in dependency declarations.
# TODO: Implementation
-# expect+1: Suffix '.c' not defined (yet)
+# expect+1: Suffix ".c" not defined (yet)
.PATH.c: ..
.SUFFIXES: .c
diff --git a/contrib/bmake/unit-tests/deptgt-posix.mk b/contrib/bmake/unit-tests/deptgt-posix.mk
index ae41af15ffec..bf29dbfbd627 100644
--- a/contrib/bmake/unit-tests/deptgt-posix.mk
+++ b/contrib/bmake/unit-tests/deptgt-posix.mk
@@ -1,16 +1,22 @@
-# $NetBSD: deptgt-posix.mk,v 1.2 2022/04/18 15:59:39 sjg Exp $
+# $NetBSD: deptgt-posix.mk,v 1.4 2022/05/07 21:24:52 rillig Exp $
#
# Tests for the special target '.POSIX', which enables POSIX mode.
#
-# As of 2022-04-18, this only means that the variable '%POSIX' is defined and
-# that the variables and rules specified by POSIX replace the default ones.
-# This is done by loading <posix.mk>, if available. That file is not included
-# in NetBSD, but only in the bmake distribution. As of 2022-04-18, POSIX
-# support is not complete.
+# As of 2022-04-18, when parsing the dependency line '.POSIX', the variable
+# '%POSIX' is defined and <posix.mk> is included, if it exists. Other than
+# that, POSIX support is still incomplete, the exact set of supported features
+# needs to be cross-checked with the POSIX specification.
#
-# Implementation node: this test needs to be isolated from the usual test
-# to prevent unit-tests/posix.mk from interfering with the posix.mk from the
-# system directory that this test uses.
+# At the point of '.POSIX:', <sys.mk> has been loaded already, unless the
+# option '-r' was given. This means that an implementation of <posix.mk> must
+# work both with and without the system rules from <sys.mk> being in effect.
+#
+# Implementation note: this test needs to run isolated from the usual tests
+# directory to prevent unit-tests/posix.mk from interfering with the posix.mk
+# from the system directory that this test uses; since at least 1997, the
+# directive '.include <file>' has been looking in the current directory first
+# before searching the file in the system search path, as described in
+# https://gnats.netbsd.org/15163.
#
# See also:
# https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html
@@ -99,7 +105,8 @@ in-first-line: .PHONY set-up-sysdir check-is-posix run
'.POSIX:'
# The only allowed lines before switching to POSIX mode are comment lines.
-# POSIX defines that empty and blank lines are called comment lines as well.
+# POSIX defines comment lines as "blank lines, empty lines, and lines with
+# <number-sign> ('#') as the first character".
all: after-comment-lines
after-comment-lines: .PHONY set-up-sysdir check-is-posix run
printf '%s\n' > ${MAIN_MK} \
diff --git a/contrib/bmake/unit-tests/deptgt-suffixes.exp b/contrib/bmake/unit-tests/deptgt-suffixes.exp
index 512e6d44a8be..9ab59fcd4810 100644
--- a/contrib/bmake/unit-tests/deptgt-suffixes.exp
+++ b/contrib/bmake/unit-tests/deptgt-suffixes.exp
@@ -26,6 +26,7 @@
.src-right.tgt-left:
: Making ${.TARGET} from ${.IMPSRC}.
+#*** End input graph for pass 1 in <curdir>:
: Making deptgt-suffixes.src-left out of nothing.
: Making deptgt-suffixes.tgt-right from deptgt-suffixes.src-left.
: Making deptgt-suffixes.src-right out of nothing.
diff --git a/contrib/bmake/unit-tests/deptgt.exp b/contrib/bmake/unit-tests/deptgt.exp
index 0a27f562293d..230fa497fcbd 100644
--- a/contrib/bmake/unit-tests/deptgt.exp
+++ b/contrib/bmake/unit-tests/deptgt.exp
@@ -1,17 +1,26 @@
-make: "deptgt.mk" line 10: warning: Extra target ignored
-make: "deptgt.mk" line 28: Unassociated shell command ": command3 # parse error, since targets == NULL"
-Parsing line 34: ${:U}: empty-source
+make: deptgt.mk:11: warning: Extra target ".PHONY" ignored
+make: deptgt.mk:30: Unassociated shell command ": command3 # parse error, since targets == NULL"
+Parsing deptgt.mk:36: ${:U}: empty-source
ParseDependency(: empty-source)
-Parsing line 35: : command for empty targets list
-Parsing line 36: : empty-source
+Parsing deptgt.mk:37: : command for empty targets list
+Parsing deptgt.mk:38: : empty-source
ParseDependency(: empty-source)
-Parsing line 37: : command for empty targets list
-Parsing line 38: .MAKEFLAGS: -d0
+Parsing deptgt.mk:39: : command for empty targets list
+Parsing deptgt.mk:40: .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
+Var_Parse: ${UNDEF}: depsrc-${UNDEF} (eval)
+Var_Parse: ${UNDEF} (eval)
+Global: .ALLTARGETS = target1 target2 sources empty-source deptgt-
+Global: .ALLTARGETS = target1 target2 sources empty-source deptgt- depsrc-
+Global: .MAKEFLAGS = -r -k -d p -d 0 -d v -d
+Global: .MAKEFLAGS = -r -k -d p -d 0 -d v -d 0
+make: deptgt.mk:51: Unknown modifier ":Z"
+ while evaluating "${:U:Z}:" with value ""
+make: deptgt.mk:55: Unknown modifier ":Z"
+ while parsing "${:U:Z}:"
+make: deptgt.mk:58: warning: Extra target "ordinary" ignored
+make: deptgt.mk:61: warning: Extra target "ordinary" ignored
+make: deptgt.mk:64: warning: Special and mundane targets don't mix. Mundane ones ignored
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "target1" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/deptgt.mk b/contrib/bmake/unit-tests/deptgt.mk
index 044644dcbd66..fd7a716e3ebc 100644
--- a/contrib/bmake/unit-tests/deptgt.mk
+++ b/contrib/bmake/unit-tests/deptgt.mk
@@ -1,4 +1,4 @@
-# $NetBSD: deptgt.mk,v 1.12 2021/12/13 23:38:54 rillig Exp $
+# $NetBSD: deptgt.mk,v 1.24 2025/06/28 22:39:28 rillig Exp $
#
# Tests for special targets like .BEGIN or .SUFFIXES in dependency
# declarations.
@@ -7,6 +7,7 @@
# Just in case anyone tries to compile several special targets in a single
# dependency line: That doesn't work, and make immediately rejects it.
+# expect+1: warning: Extra target ".PHONY" ignored
.SUFFIXES .PHONY: .c.o
# The following lines demonstrate how 'targets' is set and reset during
@@ -25,10 +26,11 @@ target1 target2: sources # targets := [target1, target2]
: command1 # targets == [target1, target2]
: command2 # targets == [target1, target2]
VAR=value # targets := NULL
+# expect+1: Unassociated shell command ": command3 # parse error, since targets == NULL"
: command3 # parse error, since targets == NULL
# In a dependency declaration, the list of targets can be empty.
-# It doesn't matter whether the empty string is generated by a variable
+# It doesn't matter whether the empty string is generated by an
# expression or whether it is just omitted.
.MAKEFLAGS: -dp
${:U}: empty-source
@@ -37,22 +39,26 @@ ${:U}: empty-source
: command for empty targets list
.MAKEFLAGS: -d0
-# Just to show that a malformed expression is only expanded once in
-# ParseDependencyTargetWord. The only way to produce an expression that
-# is well-formed on the first expansion and ill-formed on the second
-# expansion would be to use the variable modifier '::=' to modify the
-# targets. This in turn would be such an extreme and unreliable edge case
-# that nobody uses it.
-$$$$$$$${:U:Z}:
+# An expression based on an undefined variable is allowed on both sides of
+# the dependency declaration.
+.MAKEFLAGS: -dv
+deptgt-${UNDEF}: depsrc-${UNDEF}
+.MAKEFLAGS: -d0
+
+# In a dependency declaration, the whole line is expanded before interpreting
+# the line.
+# expect+1: Unknown modifier ":Z"
+${:U:Z}:
+# After expanding the line as a whole, each target is parsed but not
+# evaluated, separately, in ParseDependencyTargetWord.
+# expect+1: Unknown modifier ":Z"
+$${:U:Z}:
-# expect+1: warning: Extra target ignored
+# expect+1: warning: Extra target "ordinary" ignored
.END ordinary:
-# expect+1: warning: Extra target (ordinary) ignored
+# expect+1: warning: Extra target "ordinary" ignored
.PATH ordinary:
-# expect+1: Special and mundane targets don't mix. Mundane ones ignored
+# expect+1: warning: Special and mundane targets don't mix. Mundane ones ignored
ordinary .PATH:
-
-all:
- @:;
diff --git a/contrib/bmake/unit-tests/dir.mk b/contrib/bmake/unit-tests/dir.mk
index 36fe2baf978c..956285393489 100644
--- a/contrib/bmake/unit-tests/dir.mk
+++ b/contrib/bmake/unit-tests/dir.mk
@@ -1,8 +1,10 @@
-# $NetBSD: dir.mk,v 1.9 2021/01/23 10:48:49 rillig Exp $
+# $NetBSD: dir.mk,v 1.11 2023/12/19 19:33:40 rillig Exp $
#
# Tests for dir.c.
-.MAKEFLAGS: -m / # hide /usr/share/mk from the debug log
+# hide /usr/share/mk from the debug log
+.SYSPATH:
+.SYSPATH: /
# Dependency lines may use braces for expansion.
# See DirExpandCurly for the implementation.
@@ -65,7 +67,7 @@ fetch fetch-post extract extract-post:
# The expansions may have duplicates.
# When the source of the dependency line is expanded later, each of the
-# expanded words will be the same.
+# expanded words resolves to the same node.
all: dup-{1,1,1,1,1,1,1}
dup-1:
diff --git a/contrib/bmake/unit-tests/directive-dinclude.exp b/contrib/bmake/unit-tests/directive-dinclude.exp
index 5ea0dabb3c7e..55f1f77fbfde 100755
--- a/contrib/bmake/unit-tests/directive-dinclude.exp
+++ b/contrib/bmake/unit-tests/directive-dinclude.exp
@@ -1,4 +1,5 @@
-make: "directive-dinclude-error.inc" line 1: Invalid line type
+make: directive-dinclude-error.inc:1: Invalid line "syntax error"
+ in directive-dinclude.mk:21
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" 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 d968924a6a99..da063083235f 100755
--- a/contrib/bmake/unit-tests/directive-dinclude.mk
+++ b/contrib/bmake/unit-tests/directive-dinclude.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-dinclude.mk,v 1.2 2022/01/23 21:48:59 rillig Exp $
+# $NetBSD: directive-dinclude.mk,v 1.5 2025/06/28 22:39:28 rillig Exp $
#
# Tests for the .dinclude directive, which includes another file,
# silently skipping it if it cannot be opened. This is primarily used for
@@ -16,7 +16,7 @@
.dinclude "${MAKEFILE}/subdir"
# Errors that are not related to opening the file are still reported.
-# expect: make: "directive-dinclude-error.inc" line 1: Invalid line type
+# expect: make: directive-dinclude-error.inc:1: Invalid line "syntax error"
_!= echo 'syntax error' > directive-dinclude-error.inc
.dinclude "${.CURDIR}/directive-dinclude-error.inc"
_!= rm directive-dinclude-error.inc
diff --git a/contrib/bmake/unit-tests/directive-elif.exp b/contrib/bmake/unit-tests/directive-elif.exp
index 6856494023d7..0160665b3c01 100644
--- a/contrib/bmake/unit-tests/directive-elif.exp
+++ b/contrib/bmake/unit-tests/directive-elif.exp
@@ -1,21 +1,21 @@
-make: "directive-elif.mk" line 47: Unknown directive "elsif"
-make: "directive-elif.mk" line 52: This branch is taken.
-make: "directive-elif.mk" line 60: Unknown directive "elsif"
-make: "directive-elif.mk" line 63: This branch is taken.
-make: "directive-elif.mk" line 69: This branch is taken.
-make: "directive-elif.mk" line 89: Unknown directive "elsif"
-make: "directive-elif.mk" line 90: This misspelling is detected.
-make: "directive-elif.mk" line 91: This branch is taken because of the .else.
-make: "directive-elif.mk" line 109: What happens on misspelling in a skipped branch?
-make: "directive-elif.mk" line 119: else
-make: "directive-elif.mk" line 122: What happens on misspelling in a taken branch?
-make: "directive-elif.mk" line 124: 1-then
-make: "directive-elif.mk" line 125: Unknown directive "elsif"
-make: "directive-elif.mk" line 126: 1-elsif
-make: "directive-elif.mk" line 127: Unknown directive "elsif"
-make: "directive-elif.mk" line 128: 2-elsif
-make: "directive-elif.mk" line 134: if-less elif
-make: "directive-elif.mk" line 139: warning: extra elif
+make: directive-elif.mk:48: Unknown directive "elsif"
+make: directive-elif.mk:54: This branch is taken.
+make: directive-elif.mk:62: Unknown directive "elsif"
+make: directive-elif.mk:66: This branch is taken.
+make: directive-elif.mk:73: This branch is taken.
+make: directive-elif.mk:94: Unknown directive "elsif"
+make: directive-elif.mk:96: This misspelling is detected.
+make: directive-elif.mk:98: This branch is taken because of the .else.
+make: directive-elif.mk:117: What happens on misspelling in a skipped branch?
+make: directive-elif.mk:128: else
+make: directive-elif.mk:132: What happens on misspelling in a taken branch?
+make: directive-elif.mk:135: 1-then
+make: directive-elif.mk:137: Unknown directive "elsif"
+make: directive-elif.mk:139: 1-elsif
+make: directive-elif.mk:141: Unknown directive "elsif"
+make: directive-elif.mk:143: 2-elsif
+make: directive-elif.mk:149: if-less elif
+make: directive-elif.mk:154: warning: extra elif
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-elif.mk b/contrib/bmake/unit-tests/directive-elif.mk
index e7b89beec4e9..d6500cc1e872 100644
--- a/contrib/bmake/unit-tests/directive-elif.mk
+++ b/contrib/bmake/unit-tests/directive-elif.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-elif.mk,v 1.7 2020/12/19 19:49:01 rillig Exp $
+# $NetBSD: directive-elif.mk,v 1.8 2023/06/01 20:56:35 rillig Exp $
#
# Tests for the .elif directive.
#
@@ -44,28 +44,32 @@
# Misspelling '.elsif' below an .if branch that is taken.
.if 1
# This misspelling is in an active branch and is therefore detected.
+# expect+1: Unknown directive "elsif"
.elsif 0
# The only thing that make detects here is a misspelled directive, make
# doesn't recognize that it was meant to be a conditional directive.
# Therefore the branch continues here, even though the '.elsif' condition
# evaluates to false.
+# expect+1: This branch is taken.
. info This branch is taken.
.endif
# Misspelling '.elsif' below an .if branch that is taken.
.if 1
-# As of 2020-12-19, the misspelling is in an active branch and is therefore
-# detected.
+# The misspelling is in an active branch and is therefore detected.
+# expect+1: Unknown directive "elsif"
.elsif 1
# Since both conditions evaluate to true, this branch is taken no matter
# whether make detects a misspelling or not.
+# expect+1: This branch is taken.
. info This branch is taken.
.endif
# Misspelling '.elsif' in a skipped branch below a branch that was taken.
.if 1
+# expect+1: This branch is taken.
. info This branch is taken.
.elif 0
. info This branch is not taken.
@@ -86,8 +90,11 @@
# Misspelling '.elsif' in an .else branch that is taken.
.if 0
.else
+# expect+1: Unknown directive "elsif"
.elsif 1
+# expect+1: This misspelling is detected.
. info This misspelling is detected.
+# expect+1: This branch is taken because of the .else.
. info This branch is taken because of the .else.
.endif
@@ -106,6 +113,7 @@
.endif
+# expect+1: What happens on misspelling in a skipped branch?
.info What happens on misspelling in a skipped branch?
.if 0
. info 0-then
@@ -116,26 +124,33 @@
. info XXX: This misspelling is not detected.
. info 2-elsif
.else
+# expect+1: else
. info else
.endif
+# expect+1: What happens on misspelling in a taken branch?
.info What happens on misspelling in a taken branch?
.if 1
+# expect+1: 1-then
. info 1-then
+# expect+1: Unknown directive "elsif"
.elsif 1
+# expect+1: 1-elsif
. info 1-elsif
+# expect+1: Unknown directive "elsif"
.elsif 2
+# expect+1: 2-elsif
. info 2-elsif
.else
. info else
.endif
-# Expect: "if-less elif"
+# expect+1: if-less elif
.elif 0
.if 1
.else
-# Expect: "warning: extra elif"
+# expect+1: warning: extra elif
.elif
.endif
diff --git a/contrib/bmake/unit-tests/directive-else.exp b/contrib/bmake/unit-tests/directive-else.exp
index 17d5571ba74b..ce50b9d9d33e 100644
--- a/contrib/bmake/unit-tests/directive-else.exp
+++ b/contrib/bmake/unit-tests/directive-else.exp
@@ -1,11 +1,11 @@
-make: "directive-else.mk" line 14: The .else directive does not take arguments
-make: "directive-else.mk" line 15: ok
-make: "directive-else.mk" line 19: ok
-make: "directive-else.mk" line 21: The .else directive does not take arguments
-make: "directive-else.mk" line 26: if-less else
-make: "directive-else.mk" line 32: ok
-make: "directive-else.mk" line 33: warning: extra else
-make: "directive-else.mk" line 45: The .else directive does not take arguments
+make: directive-else.mk:14: The .else directive does not take arguments
+make: directive-else.mk:16: ok
+make: directive-else.mk:21: ok
+make: directive-else.mk:23: The .else directive does not take arguments
+make: directive-else.mk:29: if-less else
+make: directive-else.mk:36: ok
+make: directive-else.mk:38: warning: extra else
+make: directive-else.mk:51: The .else directive does not take arguments
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-else.mk b/contrib/bmake/unit-tests/directive-else.mk
index 794057110ef7..cda671907217 100644
--- a/contrib/bmake/unit-tests/directive-else.mk
+++ b/contrib/bmake/unit-tests/directive-else.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-else.mk,v 1.7 2020/12/14 22:17:11 rillig Exp $
+# $NetBSD: directive-else.mk,v 1.9 2023/11/19 21:47:52 rillig Exp $
#
# Tests for the .else directive.
#
@@ -10,26 +10,31 @@
.if 0
. warning must not be reached
-# The .else directive does not take any arguments.
+# expect+1: The .else directive does not take arguments
.else 123
+# expect+1: ok
. info ok
.endif
.if 1
+# expect+1: ok
. info ok
-# The .else directive does not take any arguments.
+# expect+1: The .else directive does not take arguments
.else 123
. warning must not be reached
.endif
# An .else without a corresponding .if is an error.
+# expect+1: if-less else
.else
# Accidental extra .else directives are detected too.
.if 0
. warning must not be reached
.else
+# expect+1: ok
. info ok
+# expect+1: warning: extra else
.else
. info After an extra .else, everything is skipped.
.endif
@@ -40,8 +45,9 @@
.else # comment
.endif
-# A variable expression does count as an argument, even if it is empty.
+# An expression does count as an argument, even if it is empty.
.if 0
+# expect+1: The .else directive does not take arguments
.else ${:U}
.endif
diff --git a/contrib/bmake/unit-tests/directive-endfor.exp b/contrib/bmake/unit-tests/directive-endfor.exp
index 7e243a8f67e6..a78ab1013a6b 100644
--- a/contrib/bmake/unit-tests/directive-endfor.exp
+++ b/contrib/bmake/unit-tests/directive-endfor.exp
@@ -1,4 +1,4 @@
-make: "directive-endfor.mk" line 9: for-less endfor
+make: directive-endfor.mk:10: 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-endfor.mk b/contrib/bmake/unit-tests/directive-endfor.mk
index b0c37f388504..93119156fb89 100644
--- a/contrib/bmake/unit-tests/directive-endfor.mk
+++ b/contrib/bmake/unit-tests/directive-endfor.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-endfor.mk,v 1.1 2020/12/30 14:50:08 rillig Exp $
+# $NetBSD: directive-endfor.mk,v 1.2 2023/06/01 20:56:35 rillig Exp $
#
# Test for the directive .endfor, which ends a .for loop.
#
@@ -6,4 +6,5 @@
# directive-for.mk
# An .endfor without a corresponding .for is a parse error.
+# expect+1: for-less endfor
.endfor
diff --git a/contrib/bmake/unit-tests/directive-endif.exp b/contrib/bmake/unit-tests/directive-endif.exp
index 0de1ecf0bf25..6a8f0bbf446f 100644
--- a/contrib/bmake/unit-tests/directive-endif.exp
+++ b/contrib/bmake/unit-tests/directive-endif.exp
@@ -1,8 +1,8 @@
-make: "directive-endif.mk" line 18: The .endif directive does not take arguments
-make: "directive-endif.mk" line 23: The .endif directive does not take arguments
-make: "directive-endif.mk" line 33: The .endif directive does not take arguments
-make: "directive-endif.mk" line 39: The .endif directive does not take arguments
-make: "directive-endif.mk" line 45: Unknown directive "endifx"
+make: directive-endif.mk:16: The .endif directive does not take arguments
+make: directive-endif.mk:21: The .endif directive does not take arguments
+make: directive-endif.mk:32: The .endif directive does not take arguments
+make: directive-endif.mk:39: The .endif directive does not take arguments
+make: directive-endif.mk:44: Unknown directive "endifx"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-endif.mk b/contrib/bmake/unit-tests/directive-endif.mk
index 10dd6ce22ce8..5e01382af5f2 100644
--- a/contrib/bmake/unit-tests/directive-endif.mk
+++ b/contrib/bmake/unit-tests/directive-endif.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-endif.mk,v 1.5 2020/12/14 21:56:17 rillig Exp $
+# $NetBSD: directive-endif.mk,v 1.7 2023/06/01 20:56:35 rillig Exp $
#
# Tests for the .endif directive.
#
@@ -8,18 +8,16 @@
# See also:
# Cond_EvalLine
-# TODO: Implementation
-
.MAKEFLAGS: -dL
-# Error: .endif does not take arguments
.if 0
-# Since 2020-12-15, complain about the extra text after the 'endif'.
+# Since 2020-12-15:
+# expect+1: The .endif directive does not take arguments
.endif 0
-# Error: .endif does not take arguments
.if 1
-# Since 2020-12-15, complain about the extra text after the 'endif'.
+# Since 2020-12-15:
+# expect+1: The .endif directive does not take arguments
.endif 1
# Comments are allowed after an '.endif'.
@@ -29,21 +27,19 @@
# Only whitespace and comments are allowed after an '.endif', but nothing
# else.
.if 1
-# Since 2020-12-15, complain about the extra text after the 'endif'.
+# Since 2020-12-15:
+# expect+1: The .endif directive does not take arguments
.endif0
# Only whitespace and comments are allowed after an '.endif', but nothing
# else.
.if 1
-# Since 2020-12-15, complain about the extra text after the 'endif'.
+# Since 2020-12-15:
+# expect+1: The .endif directive does not take arguments
.endif/
-# After an '.endif', no other letter must occur. This 'endifx' is not
-# parsed as an 'endif', therefore another '.endif' must follow to balance
-# the directives.
+# After an '.endif', no other letter must occur.
.if 1
+# expect+1: Unknown directive "endifx"
.endifx
-.endif # to close the preceding '.if'
-
-all:
- @:;
+.endif # to close the preceding '.if'
diff --git a/contrib/bmake/unit-tests/directive-error.exp b/contrib/bmake/unit-tests/directive-error.exp
index bad12326a514..4181592e4835 100644
--- a/contrib/bmake/unit-tests/directive-error.exp
+++ b/contrib/bmake/unit-tests/directive-error.exp
@@ -1,4 +1,4 @@
-make: "directive-error.mk" line 13: message
+make: directive-error.mk:14: message
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-error.mk b/contrib/bmake/unit-tests/directive-error.mk
index 135db2159dd2..f35a9da3a018 100644
--- a/contrib/bmake/unit-tests/directive-error.mk
+++ b/contrib/bmake/unit-tests/directive-error.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-error.mk,v 1.5 2021/01/27 00:02:38 rillig Exp $
+# $NetBSD: directive-error.mk,v 1.6 2023/06/01 20:56:35 rillig Exp $
#
# Tests for the .error directive, which prints an error message and exits
# immediately, unlike other "fatal" parse errors, which continue to parse
@@ -10,4 +10,5 @@
# Before parse.c 1.532 from 2021-01-27, the ".error" issued an irrelevant
# message saying "parsing warnings being treated as errors".
.MAKEFLAGS: -W
+# expect+1: message
.error message
diff --git a/contrib/bmake/unit-tests/directive-export-gmake.exp b/contrib/bmake/unit-tests/directive-export-gmake.exp
index 39a9383953dd..2c2875d669d2 100644
--- a/contrib/bmake/unit-tests/directive-export-gmake.exp
+++ b/contrib/bmake/unit-tests/directive-export-gmake.exp
@@ -1 +1,7 @@
-exit status 0
+make: directive-export-gmake.mk:71: Invalid line "export VAR=${:U1}", expanded to "export VAR=1"
+ in .for loop from directive-export-gmake.mk:67 with value = 1
+make: directive-export-gmake.mk:85: 16:00:00
+make: directive-export-gmake.mk:92: Variable/Value missing from "export"
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/directive-export-gmake.mk b/contrib/bmake/unit-tests/directive-export-gmake.mk
index d94cd9debf64..6e1d57c6a62d 100644
--- a/contrib/bmake/unit-tests/directive-export-gmake.mk
+++ b/contrib/bmake/unit-tests/directive-export-gmake.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-export-gmake.mk,v 1.3 2020/11/17 20:16:44 rillig Exp $
+# $NetBSD: directive-export-gmake.mk,v 1.10 2025/06/28 22:39:28 rillig Exp $
#
# Tests for the export directive (without leading dot), as in GNU make.
@@ -60,5 +60,47 @@ export VAR=an ${UNDEF} variable
. error
.endif
-all:
- @:;
+
+# The body of the .for loop expands to 'export VAR=${:U1}', and the 'export'
+# directive is only recognized if the line does not contain a ':', to allow
+# 'export' to be a regular target.
+.for value in 1
+# XXX: The ':' in this line is inside an expression and should thus not be
+# interpreted as a dependency operator.
+# expect+1: Invalid line "export VAR=${:U1}", expanded to "export VAR=1"
+export VAR=${value}
+.endfor
+
+
+# The 'export' directive expands expressions, but the expressions must not
+# contain a ':', due to the overly strict parser. The indirect expressions
+# may contain a ':', though.
+#
+# As a side effect, this test demonstrates that the 'export' directive exports
+# the environment variable immediately, other than the '.export' directive,
+# which defers that action if the variable value contains a '$'.
+INDIRECT_TZ= ${:UAmerica/Los_Angeles}
+export TZ=${INDIRECT_TZ}
+# expect+1: 16:00:00
+.info ${%T:L:localtime=86400}
+
+
+# The '=' must be present in the unexpanded line, it cannot be generated by
+# an expression.
+EQ= =
+# expect+1: Variable/Value missing from "export"
+export EQ_VAR${EQ}eq-value
+.if ${:!env!:MEQ_VAR=*}
+. error
+.endif
+
+
+# The variable name must be given directly, it is not expanded. The name of
+# the exported variable thus starts with a '$', and that name may be filtered
+# out by the platform.
+INDIRECT_NAME= I_NAME
+INDIRECT_VALUE= indirect value
+export ${INDIRECT_NAME}=${INDIRECT_VALUE}
+.if ${:!env!:MI_NAME=*}
+. error
+.endif
diff --git a/contrib/bmake/unit-tests/directive-export-impl.exp b/contrib/bmake/unit-tests/directive-export-impl.exp
index fada441f5e92..ff5f1663e160 100644
--- a/contrib/bmake/unit-tests/directive-export-impl.exp
+++ b/contrib/bmake/unit-tests/directive-export-impl.exp
@@ -1,18 +1,19 @@
-Parsing line 21: UT_VAR= <${REF}>
+Parsing directive-export-impl.mk:21: UT_VAR= <${REF}>
Global: UT_VAR = <${REF}>
-Parsing line 28: .export UT_VAR
+Parsing directive-export-impl.mk:28: .export UT_VAR
Global: .MAKE.EXPORTED = UT_VAR
-Parsing line 32: : ${UT_VAR:N*}
-Var_Parse: ${UT_VAR:N*} (eval-defined)
-Var_Parse: ${REF}> (eval-defined)
+Parsing directive-export-impl.mk:32: : ${UT_VAR:N*}
+Var_Parse: ${UT_VAR:N*} (eval)
+Var_Parse: ${REF}> (eval)
Evaluating modifier ${UT_VAR:N...} on value "<>"
Pattern for ':N' is "*"
ModifyWords: split "<>" into 1 word
Result of ${UT_VAR:N*} is ""
ParseDependency(: )
+Parsing directive-export-impl.mk:42: .if ${:!echo "\$UT_VAR"!} != "<>"
CondParser_Eval: ${:!echo "\$UT_VAR"!} != "<>"
-Var_Parse: ${:!echo "\$UT_VAR"!} != "<>" (eval-defined)
-Evaluating modifier ${:!...} on value "" (eval-defined, undefined)
+Var_Parse: ${:!echo "\$UT_VAR"!} != "<>" (eval-defined-loud)
+Evaluating modifier ${:!...} on value "" (eval, undefined)
Modifier part: "echo "$UT_VAR""
Capturing the output of command "echo "$UT_VAR""
Var_Parse: ${.MAKE.EXPORTED:O:u} (eval)
@@ -22,21 +23,22 @@ Evaluating modifier ${.MAKE.EXPORTED:u} on value "UT_VAR"
Result of ${.MAKE.EXPORTED:u} is "UT_VAR"
Var_Parse: ${UT_VAR} (eval)
Var_Parse: ${REF}> (eval)
-Result of ${:!echo "\$UT_VAR"!} is "<>" (eval-defined, defined)
+Result of ${:!echo "\$UT_VAR"!} is "<>" (eval, defined)
Comparing "<>" != "<>"
-Parsing line 50: : ${UT_VAR:N*}
-Var_Parse: ${UT_VAR:N*} (eval-defined)
-Var_Parse: ${REF}> (eval-defined)
+Parsing directive-export-impl.mk:50: : ${UT_VAR:N*}
+Var_Parse: ${UT_VAR:N*} (eval)
+Var_Parse: ${REF}> (eval)
Evaluating modifier ${UT_VAR:N...} on value "<>"
Pattern for ':N' is "*"
ModifyWords: split "<>" into 1 word
Result of ${UT_VAR:N*} is ""
ParseDependency(: )
-Parsing line 54: REF= defined
+Parsing directive-export-impl.mk:54: REF= defined
Global: REF = defined
+Parsing directive-export-impl.mk:58: .if ${:!echo "\$UT_VAR"!} != "<defined>"
CondParser_Eval: ${:!echo "\$UT_VAR"!} != "<defined>"
-Var_Parse: ${:!echo "\$UT_VAR"!} != "<defined>" (eval-defined)
-Evaluating modifier ${:!...} on value "" (eval-defined, undefined)
+Var_Parse: ${:!echo "\$UT_VAR"!} != "<defined>" (eval-defined-loud)
+Evaluating modifier ${:!...} on value "" (eval, undefined)
Modifier part: "echo "$UT_VAR""
Capturing the output of command "echo "$UT_VAR""
Var_Parse: ${.MAKE.EXPORTED:O:u} (eval)
@@ -46,12 +48,12 @@ Evaluating modifier ${.MAKE.EXPORTED:u} on value "UT_VAR"
Result of ${.MAKE.EXPORTED:u} is "UT_VAR"
Var_Parse: ${UT_VAR} (eval)
Var_Parse: ${REF}> (eval)
-Result of ${:!echo "\$UT_VAR"!} is "<defined>" (eval-defined, defined)
+Result of ${:!echo "\$UT_VAR"!} is "<defined>" (eval, defined)
Comparing "<defined>" != "<defined>"
-Parsing line 62: all:
+Parsing directive-export-impl.mk:62: all:
ParseDependency(all:)
Global: .ALLTARGETS = all
-Parsing line 63: .MAKEFLAGS: -d0
+Parsing directive-export-impl.mk: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-export-literal.exp b/contrib/bmake/unit-tests/directive-export-literal.exp
index c5557e363666..baece9e32217 100644
--- a/contrib/bmake/unit-tests/directive-export-literal.exp
+++ b/contrib/bmake/unit-tests/directive-export-literal.exp
@@ -1,2 +1,5 @@
value with ${UNEXPANDED} expression
+value literal
+value indirect
+value ${indirect:L}
exit status 0
diff --git a/contrib/bmake/unit-tests/directive-export-literal.mk b/contrib/bmake/unit-tests/directive-export-literal.mk
index 5fafa4a7282d..105af0102b83 100644
--- a/contrib/bmake/unit-tests/directive-export-literal.mk
+++ b/contrib/bmake/unit-tests/directive-export-literal.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-export-literal.mk,v 1.7 2020/12/13 01:07:54 rillig Exp $
+# $NetBSD: directive-export-literal.mk,v 1.8 2024/06/01 18:44:05 rillig Exp $
#
# Tests for the .export-literal directive, which exports a variable value
# without expanding it.
@@ -9,5 +9,28 @@ UT_VAR= value with ${UNEXPANDED} expression
.export-literal # oops: missing argument
+# After a variable whose value does not contain a '$' is exported, a following
+# .export-literal can be skipped, to avoid a setenv call, which may leak
+# memory on some platforms.
+UT_TWICE_LITERAL= value literal
+.export UT_TWICE_LITERAL
+.export-literal UT_TWICE_LITERAL
+
+# XXX: After an .export, an .export-literal has no effect, even when the
+# variable value contains a '$'.
+UT_TWICE_EXPR= value ${indirect:L}
+.export UT_TWICE_EXPR
+.export-literal UT_TWICE_EXPR
+
+# After an .export, an .unexport resets the variable's exported state,
+# re-enabling a later .export-literal.
+UT_TWICE_EXPR_UNEXPORT= value ${indirect:L}
+.export UT_TWICE_EXPR_UNEXPORT
+.unexport UT_TWICE_EXPR_UNEXPORT
+.export-literal UT_TWICE_EXPR_UNEXPORT
+
all:
@echo "$$UT_VAR"
+ @echo "$$UT_TWICE_LITERAL"
+ @echo "$$UT_TWICE_EXPR"
+ @echo "$$UT_TWICE_EXPR_UNEXPORT"
diff --git a/contrib/bmake/unit-tests/directive-export.exp b/contrib/bmake/unit-tests/directive-export.exp
index 39a9383953dd..b4bada820ccd 100644
--- a/contrib/bmake/unit-tests/directive-export.exp
+++ b/contrib/bmake/unit-tests/directive-export.exp
@@ -1 +1,5 @@
+make: directive-export.mk:36: warning: .export requires an argument.
+make: directive-export.mk:60: 00:00:00
+make: directive-export.mk:65: 00:00:00
+make: directive-export.mk:68: 16:00:00
exit status 0
diff --git a/contrib/bmake/unit-tests/directive-export.mk b/contrib/bmake/unit-tests/directive-export.mk
index 942d4b371bbd..d2ed7a1a1efb 100644
--- a/contrib/bmake/unit-tests/directive-export.mk
+++ b/contrib/bmake/unit-tests/directive-export.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-export.mk,v 1.8 2021/02/16 19:01:18 rillig Exp $
+# $NetBSD: directive-export.mk,v 1.12 2024/06/01 10:06:23 rillig Exp $
#
# Tests for the .export directive.
#
@@ -28,17 +28,44 @@ VAR= value $$ ${INDIRECT}
. error
.endif
-# No syntactical argument means to export all variables.
+# Before var.c 1.1117 from 2024-06-01, a plain ".export" without a syntactical
+# argument exported all global variables. This case could be triggered
+# unintentionally by writing a line of the form ".export ${VARNAMES}" to a
+# makefile, when VARNAMES was an empty list.
+# expect+1: warning: .export requires an argument.
.export
# An empty argument means no additional variables to export.
.export ${:U}
-# Trigger the "This isn't going to end well" in ExportVarEnv.
+# Before a child process is started, whether for the '!=' assignment operator
+# or for the ':sh' modifier, all variables that were marked for being exported
+# are expanded and then exported. If expanding such a variable requires
+# running a child command, the marked-as-exported variables would need to be
+# exported first, ending in an endless loop. To avoid this endless loop,
+# don't export the variables while preparing a child process, see
+# ExportVarEnv.
EMPTY_SHELL= ${:sh}
.export EMPTY_SHELL # only marked for export at this point
_!= :;: # Force the variable to be actually exported.
+# If the '.export' directive exports a variable whose value contains a '$',
+# the actual export action is deferred until a subprocess is started, assuming
+# that only subprocesses access the environment variables. The ':localtime'
+# modifier depends on the 'TZ' environment variable, without any subprocess.
+export TZ=${UTC}
+# expect+1: 00:00:00
+.info ${%T:L:localtime=86400}
+INDIRECT_TZ= ${:UAmerica/Los_Angeles}
+TZ= ${INDIRECT_TZ}
+.export TZ
+# expect+1: 00:00:00
+.info ${%T:L:localtime=86400}
+_!= echo 'force exporting the environment variables'
+# expect+1: 16:00:00
+.info ${%T:L:localtime=86400}
+
+
all:
diff --git a/contrib/bmake/unit-tests/directive-for-break.exp b/contrib/bmake/unit-tests/directive-for-break.exp
new file mode 100644
index 000000000000..cf46837df6c6
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-for-break.exp
@@ -0,0 +1,6 @@
+make: directive-for-break.mk:45: break outside of for loop
+make: directive-for-break.mk:65: The .break directive does not take arguments
+ in .for loop from directive-for-break.mk:63 with i = 1
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/directive-for-break.mk b/contrib/bmake/unit-tests/directive-for-break.mk
new file mode 100644
index 000000000000..d9290d38c215
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-for-break.mk
@@ -0,0 +1,66 @@
+# $NetBSD: directive-for-break.mk,v 1.5 2023/06/01 20:56:35 rillig Exp $
+#
+# Tests for .break in .for loops, which immediately terminates processing of
+# the surrounding .for loop.
+
+
+# .break terminates the loop early.
+# This is usually done within a conditional.
+.for i in 1 2 3 4 5 6 7 8
+. if $i == 3
+I= $i
+. break
+I= unreached
+. endif
+.endfor
+.if $I != "3"
+. error
+.endif
+
+
+# The .break only breaks out of the immediately surrounding .for loop, any
+# other .for loops are continued normally.
+.for outer in o1 o2 o3
+. for inner in i1 i2 i3
+. if ${outer} == o2 && ${inner} == i2
+. break
+. endif
+COMBINED+= ${outer}-${inner}
+. endfor
+.endfor
+# Only o2-i2 and o2-i3 are missing.
+.if ${COMBINED} != "o1-i1 o1-i2 o1-i3 o2-i1 o3-i1 o3-i2 o3-i3"
+. error
+.endif
+
+
+# A .break outside the context of a .for loop is an error.
+.if $I == 0
+# No parse error, even though the .break occurs outside a .for loop, since
+# lines from inactive branches are only parsed as far as necessary to see
+# whether they belong to an .if/.elif/.else/.endif chain.
+. break
+.else
+# expect+1: break outside of for loop
+. break
+.endif
+
+
+# Since cond.c 1.335 from 2022-09-02 and before cond.c 1.338 from 2022-09-23,
+# the following paragraph generated the wrong error message '4294967294 open
+# conditionals'.
+.if 1
+. if 2
+. for var in value
+. if 3
+. break
+. endif
+. endfor
+. endif
+.endif
+
+
+.for i in 1
+# expect+1: The .break directive does not take arguments
+. break 1
+.endfor
diff --git a/contrib/bmake/unit-tests/directive-for-empty.exp b/contrib/bmake/unit-tests/directive-for-empty.exp
new file mode 100644
index 000000000000..ae8e97edb777
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-for-empty.exp
@@ -0,0 +1,22 @@
+make: directive-for-empty.mk:22: 2
+For: end for 1
+For: loop body with i = value:
+# The identifier 'empty' can only be used in conditions such as .if, .ifdef or
+# .elif. In other lines the string 'empty(' must be preserved.
+CPPFLAGS+= -Dmessage="empty(i)"
+# There may be whitespace between 'empty' and '('.
+.if ! empty (i)
+. error
+.endif
+# Even in conditions, the string 'empty(' is not always a function call, it
+# can occur in a string literal as well.
+.if "empty\(i)" != "empty(i)"
+. error
+.endif
+# In comments like 'empty(i)', the text must be preserved as well.
+#
+# Conditions, including function calls to 'empty', can not only occur in
+# condition directives, they can also occur in the modifier ':?', see
+# varmod-ifelse.mk.
+CPPFLAGS+= -Dmacro="${empty(i):?empty:not-empty}"
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-for-empty.mk b/contrib/bmake/unit-tests/directive-for-empty.mk
new file mode 100644
index 000000000000..886f7430c682
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-for-empty.mk
@@ -0,0 +1,126 @@
+# $NetBSD: directive-for-empty.mk,v 1.4 2024/05/31 07:13:12 rillig Exp $
+#
+# Tests for .for loops containing conditions of the form 'empty(var:...)'.
+#
+# When a .for loop is expanded, expressions in the body of the loop
+# are replaced with expressions containing the variable values. This
+# replacement is a bit naive but covers most of the practical cases. The one
+# popular exception is the condition 'empty(var:Modifiers)', which does not
+# look like an expression and is thus not replaced.
+#
+# See also:
+# https://gnats.netbsd.org/43821
+
+
+# In the body of the .for loop, the expression '${i:M*2*}' is replaced with
+# '${:U11:M*2*}', '${:U12:M*2*}', '${:U13:M*2*}', one after another. This
+# replacement creates the impression that .for variables were real variables,
+# when in fact they aren't.
+.for i in 11 12 13
+. if ${i:M*2*}
+# expect+1: 2
+.info 2
+. endif
+.endfor
+
+
+# In conditions, the function call to 'empty' does not look like an
+# expression, therefore it is not replaced. Since there is no global variable
+# named 'i', this condition makes for a leaky abstraction. If the .for
+# variables were real variables, calling 'empty' would work on them as well.
+.for i in 11 12 13
+# Asking for an empty iteration variable does not make sense as the .for loop
+# splits the iteration items into words, and such a word cannot be empty.
+. if !empty(i)
+. error # not reached, due to the leaky abstraction
+. endif
+# The typical way of mistakenly using 'empty' with variables from .for loops
+# is pattern matching using the modifiers ':M' or ':N'.
+. if !empty(i:M*2*)
+. error
+. endif
+# Instead of the 'empty' function, the variables from .for loops can be
+# queried using conditions of the form '${var:...} != ""'.
+. if $i == "12" && ${i:M*2*} != "12"
+. error
+. endif
+.endfor
+
+
+# The idea of replacing every occurrences of 'empty(i' in the body of a .for
+# loop would be naive and require many special cases, as there are many cases
+# that need to be considered when deciding whether the token 'empty' is a
+# function call or not, as demonstrated by the following examples. For
+# expressions like '${i:Modifiers}', this is simpler as a single
+# dollar almost always starts an expression. For counterexamples and
+# edge cases, see directive-for-escape.mk. Adding another such tricky detail
+# is out of the question.
+.MAKEFLAGS: -df
+.for i in value
+# The identifier 'empty' can only be used in conditions such as .if, .ifdef or
+# .elif. In other lines the string 'empty(' must be preserved.
+CPPFLAGS+= -Dmessage="empty(i)"
+# There may be whitespace between 'empty' and '('.
+.if ! empty (i)
+. error
+.endif
+# Even in conditions, the string 'empty(' is not always a function call, it
+# can occur in a string literal as well.
+.if "empty\(i)" != "empty(i)"
+. error
+.endif
+# In comments like 'empty(i)', the text must be preserved as well.
+#
+# Conditions, including function calls to 'empty', can not only occur in
+# condition directives, they can also occur in the modifier ':?', see
+# varmod-ifelse.mk.
+CPPFLAGS+= -Dmacro="${empty(i):?empty:not-empty}"
+.endfor
+.MAKEFLAGS: -d0
+
+
+# An idea to work around the above problems is to collect the variables from
+# the .for loops in a separate scope. To match the current behavior, there
+# has to be one scope per included file. There may be .for loops using the
+# same variable name in files that include each other:
+#
+# outer.mk: .for i in outer
+# . info $i # outer
+# . include "inner.mk"
+# inner.mk: . info $i # (undefined)
+# . for i in inner
+# . info $i # inner
+# . endfor
+# . info $i # (undefined)
+# outer.mk: . info $i # outer
+# .endfor
+#
+# This might be regarded another leaky abstraction, but it is in fact useful
+# that variables from .for loops can only affect expressions in the current
+# file. If variables from .for loops were implemented as global variables,
+# they might interact between files.
+#
+# To emulate this exact behavior for the function 'empty', each file in the
+# stack of included files needs its own scope that is independent from the
+# other files.
+#
+# Another tricky detail are nested .for loops in a single file that use the
+# same variable name. These are generally avoided by developers, as they
+# would be difficult to understand for humans as well. Technically, they are
+# possible though. Assuming there are two nested .for loops, both using the
+# variable 'i'. When the inner .for loop ends, the inner 'i' needs to be
+# removed from the scope, which would need to make the outer 'i' visible
+# again. This would suggest to use one variable scope per .for loop.
+#
+# Using a separate scope has the benefit that Var_Parse already allows for
+# a custom scope to be passed as parameter. This would have another side
+# effect though. There are several modifiers that actually modify variables,
+# and these modifications happen in the scope that is passed to Var_Parse.
+# This would mean that the combination of a .for variable and the modifiers
+# '::=', '::+=', '::?=', '::!=' and ':_' would lead to different behavior than
+# before.
+
+# TODO: Add code that demonstrates the current interaction between variables
+# from .for loops and the modifiers mentioned above.
+
+all:
diff --git a/contrib/bmake/unit-tests/directive-for-errors.exp b/contrib/bmake/unit-tests/directive-for-errors.exp
index da5eee473ec2..1bae631b967a 100644
--- a/contrib/bmake/unit-tests/directive-for-errors.exp
+++ b/contrib/bmake/unit-tests/directive-for-errors.exp
@@ -1,22 +1,15 @@
-make: "directive-for-errors.mk" line 7: Unknown directive "fori"
-make: "directive-for-errors.mk" line 8: warning:
-make: "directive-for-errors.mk" line 9: for-less endfor
-make: "directive-for-errors.mk" line 19: Unknown directive "for"
-make: "directive-for-errors.mk" line 20: warning:
-make: "directive-for-errors.mk" line 21: for-less endfor
-make: "directive-for-errors.mk" line 37: Dollar $ 1 1 and backslash 2 2 2.
-make: "directive-for-errors.mk" line 37: Dollar $ 3 3 and backslash 4 4 4.
-make: "directive-for-errors.mk" line 43: no iteration variables in for
-make: "directive-for-errors.mk" line 47: warning: Should not be reached.
-make: "directive-for-errors.mk" line 48: for-less endfor
-make: "directive-for-errors.mk" line 53: Wrong number of words (5) in .for substitution list with 3 variables
-make: "directive-for-errors.mk" line 64: missing `in' in for
-make: "directive-for-errors.mk" line 66: warning: Should not be reached.
-make: "directive-for-errors.mk" line 67: for-less endfor
-make: "directive-for-errors.mk" line 73: Unknown modifier "Z"
-make: "directive-for-errors.mk" line 74: warning: Should not be reached.
-make: "directive-for-errors.mk" line 74: warning: Should not be reached.
-make: "directive-for-errors.mk" line 74: warning: Should not be reached.
+make: directive-for-errors.mk:9: Unknown directive "fori"
+make: directive-for-errors.mk:11: warning: <>
+make: directive-for-errors.mk:13: for-less endfor
+make: directive-for-errors.mk:25: Unknown directive "for"
+make: directive-for-errors.mk:27: warning: <>
+make: directive-for-errors.mk:29: for-less endfor
+make: directive-for-errors.mk:44: Invalid character "$" in .for loop variable name
+make: directive-for-errors.mk:52: Missing iteration variables in .for loop
+make: directive-for-errors.mk:64: Wrong number of words (5) in .for substitution list with 3 variables
+make: directive-for-errors.mk:78: Missing "in" in .for loop
+make: directive-for-errors.mk:85: Unknown modifier ":Z"
+ while evaluating "${:U3:Z} 4" with value "3"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-for-errors.mk b/contrib/bmake/unit-tests/directive-for-errors.mk
index 602ecbf32e4e..2571b600bf38 100644
--- a/contrib/bmake/unit-tests/directive-for-errors.mk
+++ b/contrib/bmake/unit-tests/directive-for-errors.mk
@@ -1,45 +1,54 @@
-# $NetBSD: directive-for-errors.mk,v 1.3 2021/04/04 10:13:09 rillig Exp $
+# $NetBSD: directive-for-errors.mk,v 1.18 2025/06/28 22:39:28 rillig Exp $
#
# Tests for error handling in .for loops.
+
# A .for directive must be followed by whitespace, everything else results
# in a parse error.
+# expect+1: Unknown directive "fori"
.fori in 1 2 3
-. warning ${i}
+# expect+1: warning: <>
+. warning <${i}>
+# expect+1: for-less endfor
.endfor
+
# A slash is not whitespace, therefore this is not parsed as a .for loop.
#
# XXX: The error message is misleading though. As of 2020-12-31, it says
-# "Unknown directive "for"", but that directive is actually known. This is
+# 'Unknown directive "for"', but that directive is actually known. This is
# because ForEval does not detect the .for loop as such, so parsing
# continues in ParseLine > ParseDependencyLine > ParseDependency >
# ParseDependencyTargets > ParseErrorNoDependency, and there the directive
# name is parsed a bit differently.
+# expect+1: Unknown directive "for"
.for/i in 1 2 3
-. warning ${i}
+# expect+1: warning: <>
+. warning <${i}>
+# expect+1: for-less endfor
.endfor
-# As of 2020-12-31, the variable name can be an arbitrary word, it just needs
-# to be separated by whitespace. Even '$' and '\' are valid variable names,
-# which is not useful in practice.
-#
-# The '$$' is not replaced with the values '1' or '3' from the .for loop,
-# instead it is kept as-is, and when the .info directive expands its argument,
-# each '$$' gets replaced with a single '$'. The "long variable expression"
-# ${$} gets replaced though, even though this would be a parse error everywhere
-# outside a .for loop.
+
+# Before for.c 1.173 from 2023-05-08, the variable name could be an arbitrary
+# word, it only needed to be separated by whitespace. Even '$' and '\' were
+# valid variable names, which was not useful in practice.
#
-# The '\' on the other hand is treated as a normal variable name.
+# The '$$' was not replaced with the values '1' or '3' from the .for loop,
+# instead it was kept as-is, and when the .info directive expanded its
+# argument, each '$$' got replaced with a single '$'. The "long
+# expression" ${$} got replaced though, even though this would be a parse
+# error everywhere outside a .for loop.
${:U\$}= dollar # see whether the "variable" '$' is local
${:U\\}= backslash # see whether the "variable" '\' is local
-.for $ \ in 1 2 3 4
+# expect+1: Invalid character "$" in .for loop variable name
+.for a b $ \ in 1 2 3 4
. info Dollar $$ ${$} $($) and backslash $\ ${\} $(\).
.endfor
# If there are no variables, there is no point in expanding the .for loop
-# since this would end up in an endless loop, each time consuming 0 of the
-# 3 values.
+# since this would end up in an endless loop, consuming 0 of the 3 values in
+# each iteration.
+# expect+1: Missing iteration variables in .for loop
.for in 1 2 3
# XXX: This should not be reached. It should be skipped, as already done
# when the number of values is not a multiple of the number of variables,
@@ -47,29 +56,32 @@ ${:U\\}= backslash # see whether the "variable" '\' is local
. warning Should not be reached.
.endfor
+
# There are 3 variables and 5 values. These 5 values cannot be split evenly
# among the variables, therefore the loop is not expanded at all, it is
-# rather skipped.
+# skipped instead.
+# expect+1: Wrong number of words (5) in .for substitution list with 3 variables
.for a b c in 1 2 3 4 5
. warning Should not be reached.
.endfor
+
# The list of values after the 'in' may be empty, no matter if this emptiness
-# comes from an empty expansion or even from a syntactically empty line.
+# comes from an expanded expression or from a syntactically empty line.
.for i in
. info Would be reached if there were items to loop over.
.endfor
-# A missing 'in' should parse the .for loop but skip the body.
-.for i : k
-# XXX: As of 2020-12-31, this line is reached once.
-. warning Should not be reached.
+
+# A missing 'in' parses the .for loop but skips the body.
+# expect+1: Missing "in" in .for loop
+.for i over k
+. error
.endfor
-# A malformed modifier should be detected and skip the body of the loop.
-#
-# XXX: As of 2020-12-31, Var_Subst doesn't report any errors, therefore
-# the loop body is expanded as if no error had happened.
+
+# An error in the items skips the body of the loop.
+# expect+1: Unknown modifier ":Z"
.for i in 1 2 ${:U3:Z} 4
-. warning Should not be reached.
+. error
.endfor
diff --git a/contrib/bmake/unit-tests/directive-for-escape.exp b/contrib/bmake/unit-tests/directive-for-escape.exp
index 5fa4ae1ed877..78cc57a738a2 100644
--- a/contrib/bmake/unit-tests/directive-for-escape.exp
+++ b/contrib/bmake/unit-tests/directive-for-escape.exp
@@ -1,146 +1,170 @@
For: end for 1
-For: loop body:
+For: loop body with chars = !"#$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~:
. info ${:U!"#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~}
-make: Unclosed variable expression, expecting '}' for modifier "U!"" of variable "" with value "!""
-make: "directive-for-escape.mk" line 19: !"
+make: directive-for-escape.mk:21: Unclosed expression, expecting "}" for modifier "U!""
+ while evaluating "${:U!"" with value "!""
+ in .for loop from directive-for-escape.mk:20 with chars = !"#$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~
+make: directive-for-escape.mk:21: !"
For: end for 1
-For: loop body:
+For: loop body with chars = !"\\#$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~:
. info ${:U!"\\\\#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~}
-make: Unclosed variable expression, expecting '}' for modifier "U!"\\\\" of variable "" with value "!"\\"
-make: "directive-for-escape.mk" line 29: !"\\
+make: directive-for-escape.mk:33: Unclosed expression, expecting "}" for modifier "U!"\\\\"
+ while evaluating "${:U!"\\\\" with value "!"\\"
+ in .for loop from directive-for-escape.mk:32 with chars = !"\\#$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~
+make: directive-for-escape.mk:33: !"\\
For: end for 1
-For: loop body:
+For: loop body with i = $:
. info ${:U\$}
-make: "directive-for-escape.mk" line 43: $
-For: loop body:
+make: directive-for-escape.mk:57: $
+For: loop body with i = ${V}:
. info ${:U${V}}
-make: "directive-for-escape.mk" line 43: value
-For: loop body:
+make: directive-for-escape.mk:57: value
+For: loop body with i = ${V:=-with-modifier}:
. info ${:U${V:=-with-modifier}}
-make: "directive-for-escape.mk" line 43: value-with-modifier
-For: loop body:
+make: directive-for-escape.mk:57: value-with-modifier
+For: loop body with i = $(V):
. info ${:U$(V)}
-make: "directive-for-escape.mk" line 43: value
-For: loop body:
+make: directive-for-escape.mk:57: value
+For: loop body with i = $(V:=-with-modifier):
. info ${:U$(V:=-with-modifier)}
-make: "directive-for-escape.mk" line 43: value-with-modifier
+make: directive-for-escape.mk:57: value-with-modifier
For: end for 1
-For: loop body:
+For: loop body with i = $:
+. info ${:U\$}
+make: directive-for-escape.mk:69: $
+For: loop body with i = ${V}:
+. info ${:U${V}}
+make: directive-for-escape.mk:69: value
+For: loop body with i = ${V:=-with-modifier}:
+. info ${:U${V:=-with-modifier}}
+make: directive-for-escape.mk:69: value-with-modifier
+For: loop body with i = $(V):
+. info ${:U$(V)}
+make: directive-for-escape.mk:69: value
+For: loop body with i = $(V:=-with-modifier):
+. info ${:U$(V:=-with-modifier)}
+make: directive-for-escape.mk:69: value-with-modifier
+For: end for 1
+For: loop body with i = ${UNDEF:U\$\$:
+# ${:U\${UNDEF\:U\\$\\$}
+For: loop body with i = {{}}:
+# ${:U{{\}\}}
+For: loop body with i = end}:
+# ${:Uend\}}
+For: end for 1
+For: loop body with i = ${UNDEF:U\$\$:
. info ${:U\${UNDEF\:U\\$\\$}
-make: "directive-for-escape.mk" line 72: ${UNDEF:U\backslash$
-For: loop body:
+make: directive-for-escape.mk:120: ${UNDEF:U\backslash$
+For: loop body with i = {{}}:
. info ${:U{{\}\}}
-make: "directive-for-escape.mk" line 72: {{}}
-For: loop body:
+make: directive-for-escape.mk:120: {{}}
+For: loop body with i = end}:
. info ${:Uend\}}
-make: "directive-for-escape.mk" line 72: end}
+make: directive-for-escape.mk:120: end}
For: end for 1
-For: loop body:
+For: loop body with i = begin<${UNDEF:Ufallback:N{{{}}}}>end:
. info ${:Ubegin<${UNDEF:Ufallback:N{{{}}}}>end}
-make: "directive-for-escape.mk" line 84: begin<fallback>end
+make: directive-for-escape.mk:138: begin<fallback>end
For: end for 1
-For: loop body:
+For: loop body with i = $:
. info ${:U\$}
-make: "directive-for-escape.mk" line 92: $
-For: end for 1
-For: loop body:
-. info ${NUMBERS} ${:Ureplaced}
-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 110: replaced
-For: end for 1
-For: loop body:
-. info . $$i: ${:Uinner}
-. info . $${i}: ${:Uinner}
-. info . $${i:M*}: ${:Uinner:M*}
-. info . $$(i): $(:Uinner)
-. info . $$(i:M*): $(:Uinner:M*)
-. info . $${i$${:U}}: ${i${:U}}
-. info . $${i\}}: ${:Uinner\}} # XXX: unclear why ForLoop_SubstVarLong needs this
-. info . $${i2}: ${i2}
-. info . $${i,}: ${i,}
-. info . adjacent: ${:Uinner}${:Uinner}${:Uinner:M*}${:Uinner}
-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 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 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 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 = "
+make: directive-for-escape.mk:147: $
+make: directive-for-escape.mk:155: Invalid character ":" in .for loop variable name
+For: end for 1
+make: directive-for-escape.mk:165: Invalid character "}" in .for loop variable name
+For: end for 1
+For: end for 1
+For: loop body with i = inner:
+. info ${:Uinner} ${:Uinner} ${:Uinner:M*} $(:Uinner) $(:Uinner:M*)
+make: directive-for-escape.mk:175: inner inner inner inner inner
+For: end for 1
+For: loop body with i = inner:
+. info ${i${:U}}
+make: directive-for-escape.mk:179: outer
+For: end for 1
+For: loop body with i = inner:
+. info ${:Uinner\}} # XXX: unclear why ForLoop_SubstVarLong needs this
+make: directive-for-escape.mk:183: inner}
+For: end for 1
+For: loop body with i = inner:
+. info ${i2} ${i,} ${:Uinner}${:Uinner}${:Uinner:M*}${:Uinner}
+make: directive-for-escape.mk:187: two comma innerinnerinnerinner
+make: directive-for-escape.mk:196: Invalid character "$" in .for loop variable name
+For: end for 1
+make: directive-for-escape.mk:208: eight and no cents.
+For: end for 1
+make: directive-for-escape.mk:222: newline in .for value
+ in .for loop from directive-for-escape.mk:222 with i = "
+"
+make: directive-for-escape.mk:222: newline in .for value
+ in .for loop from directive-for-escape.mk:222 with i = "
"
-For: loop body:
+For: loop body with i = "
+":
+. info short: ${:U" "}, long: ${:U" "}
+make: directive-for-escape.mk:223: short: " ", long: " "
+For: end for 1
+For: loop body with i = "
+":
+Parsing directive-for-escape.mk:236: .for i in "${.newline}"
+For: end for 1
+Parse_PushInput: .for loop in directive-for-escape.mk:236
+make: directive-for-escape.mk:236: newline in .for value
+ in .for loop from directive-for-escape.mk:236 with i = "
+"
+For: loop body with i = "
+":
: ${:U" "}
SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `directive-for-escape.mk'
-Parsing line 168: : ${:U" "}
+Parsing directive-for-escape.mk:237: : ${:U" "}
ParseDependency(: " ")
-ParseEOF: returning to file directive-for-escape.mk, line 170
+ParseEOF: returning to directive-for-escape.mk:239
SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `directive-for-escape.mk'
-Parsing line 170: .MAKEFLAGS: -d0
+Parsing directive-for-escape.mk:239: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
For: end for 1
-For: loop body:
+For: loop body with i = #:
# ${:U#}
-For: loop body:
+For: loop body with i = \\#:
# ${:U\\\\#}
For: end for 1
-For: loop body:
+For: loop body with i = $:
# ${:U\$}
-For: loop body:
+For: loop body with i = $i:
# ${:U$i}
-For: loop body:
+For: loop body with i = $(i):
# ${:U$(i)}
-For: loop body:
+For: loop body with i = ${i}:
# ${:U${i}}
-For: loop body:
+For: loop body with i = $$:
# ${:U$$}
-For: loop body:
+For: loop body with i = $$$$:
# ${:U$$$$}
-For: loop body:
+For: loop body with i = ${:U\$\$}:
# ${:U${:U\$\$}}
For: end for 1
-For: loop body:
+For: loop body with i = ${.TARGET}:
# ${:U${.TARGET}}
-For: loop body:
+For: loop body with i = ${.TARGET}:
# ${:U${.TARGET}}
-For: loop body:
+For: loop body with i = $${.TARGET}:
# ${:U$${.TARGET\}}
-For: loop body:
+For: loop body with i = $${.TARGET}:
# ${:U$${.TARGET\}}
For: end for 1
-For: loop body:
+For: loop body with i = (((:
# ${:U(((}
-For: loop body:
+For: loop body with i = {{{:
# ${:U{{{}
-For: loop body:
+For: loop body with i = ))):
# ${:U)))}
-For: loop body:
+For: loop body with i = }}}:
# ${:U\}\}\}}
+For: end for 1
+For: loop body with , = 1:
+# $$i $i
+# VAR= $$i $i ${a:S,from${:U1}to,}
+VAR= $$i $i ${a:S,from${:U1}to,}
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 03a7a16b6a7b..913d61831c46 100644
--- a/contrib/bmake/unit-tests/directive-for-escape.mk
+++ b/contrib/bmake/unit-tests/directive-for-escape.mk
@@ -1,9 +1,8 @@
-# $NetBSD: directive-for-escape.mk,v 1.15 2022/01/27 20:15:14 rillig Exp $
+# $NetBSD: directive-for-escape.mk,v 1.30 2025/06/28 22:39:28 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
-# escaping and unescaping must pass all characters and strings effectively
-# unmodified.
+# escaping and unescaping must pass all characters and strings unmodified.
.MAKEFLAGS: -df
@@ -12,9 +11,12 @@
# This could be considered a bug.
ASCII= !"\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~
+
# XXX: As of 2020-12-31, the '#' is not preserved in the expanded body of
# the loop. Not only would it need the escaping for the variable modifier
# ':U' but also the escaping for the line-end comment.
+# expect+3: Unclosed expression, expecting "}" for modifier "U!""
+# expect+2: !"
.for chars in ${ASCII}
. info ${chars}
.endfor
@@ -25,6 +27,8 @@ ASCII= !"\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~
# This means that a '#' sign cannot be passed in the value of a .for loop
# at all.
ASCII.2020-12-31= !"\\\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~
+# expect+3: Unclosed expression, expecting "}" for modifier "U!"\\\\"
+# expect+2: !"\\
.for chars in ${ASCII.2020-12-31}
. info ${chars}
.endfor
@@ -39,47 +43,97 @@ ASCII.2020-12-31= !"\\\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~
# See for.c, function ExprLen.
V= value
VALUES= $$ $${V} $${V:=-with-modifier} $$(V) $$(V:=-with-modifier)
+# expect: . info ${:U\$}
+# expect+10: $
+# expect: . info ${:U${V}}
+# expect+8: value
+# expect: . info ${:U${V:=-with-modifier}}
+# expect+6: value-with-modifier
+# expect: . info ${:U$(V)}
+# expect+4: value
+# expect: . info ${:U$(V:=-with-modifier)}
+# expect+2: value-with-modifier
.for i in ${VALUES}
. info $i
.endfor
+#
+# Providing the loop items directly has the same effect.
+# expect: . info ${:U\$}
+# expect+7: $
+# expect: . info ${:U${V}}
+# expect+5: value
+# expect+4: value-with-modifier
+# expect+3: value
+# expect+2: value-with-modifier
+.for i in $$ $${V} $${V:=-with-modifier} $$(V) $$(V:=-with-modifier)
+. info $i
+.endfor
# Try to cover the code for nested '{}' in ExprLen, without success.
#
-# The value of the variable VALUES is not meant to be a variable expression.
+# The value of the variable VALUES is not meant to be an expression.
# Instead, it is meant to represent literal text, the only escaping mechanism
# being that each '$' is written as '$$'.
+VALUES= $${UNDEF:U\$$\$$ {{}} end}
#
# The .for loop splits ${VALUES} into 3 words, at the space characters, since
# 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.
+#
+# Each of these words is now inserted in the body of the .for loop.
+.for i in ${VALUES}
+# $i
+.endfor
+#
+# When these words are injected into the body of the .for loop, each inside a
+# '${:U...}' expression, the result is:
+#
+# expect: For: loop body with i = ${UNDEF:U\$\$:
+# expect: # ${:U\${UNDEF\:U\\$\\$}
+# expect: For: loop body with i = {{}}:
+# expect: # ${:U{{\}\}}
+# expect: For: loop body with i = end}:
+# expect: # ${:Uend\}}
+# expect: For: end for 1
+#
+# The first of these expressions is the most interesting one, due to its many
+# special characters. This expression is properly balanced:
+#
+# Text Meaning Explanation
+# \$ $ escaped
+# { { ordinary text
+# UNDEF UNDEF ordinary text
+# \: : escaped
+# U U ordinary text
+# \\ \ escaped
+# $\ (expr) an expression, the variable name is '\'
+# \$ $ escaped
+#
+# To make the expression '$\' visible, define it to an actual word:
${: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 '\$'?
+# expect+3: {{}}
+# expect+2: end}
.for i in ${VALUES}
. info $i
.endfor
+#
+# FIXME: There was no expression '$\' in the original text of the variable
+# 'VALUES', that's a surprise in the parser.
-# Second try to cover the code for nested '{}' in ExprLen.
+
+# The second attempt to cover the code for nested '{}' in ExprLen.
#
-# XXX: It is wrong that ExprLen requires the braces to be balanced.
+# XXX: It is not the job of ExprLen to parse an expression, it is naive to
+# expect ExprLen to get all the details right in just a few lines of code.
# Each variable modifier has its own inconsistent way of parsing nested
-# variable expressions, braces and parentheses. (Compare ':M', ':S', and
+# expressions, braces and parentheses. (Compare ':M', ':S', and
# ':D' for details.) The only sensible thing to do is therefore to let
# Var_Parse do all the parsing work.
VALUES= begin<$${UNDEF:Ufallback:N{{{}}}}>end
+# expect+2: begin<fallback>end
.for i in ${VALUES}
. info $i
.endfor
@@ -88,24 +142,26 @@ VALUES= begin<$${UNDEF:Ufallback:N{{{}}}}>end
# The dollar sign is correctly passed through to the body of the .for loop.
# There, it is expanded by the .info directive, but even there a trailing
# dollar sign is kept as-is.
+# expect+2: $
.for i in ${:U\$}
. info ${i}
.endfor
-# As of 2020-12-31, the name of the iteration variable can even contain
-# colons, which then affects variable expressions having this exact modifier.
-# This is clearly an unintended side effect of the implementation.
+# Before for.c 1.173 from 2023-05-08, the name of the iteration variable
+# could contain colons, which affected expressions having this exact
+# modifier. This possibility was neither intended nor documented.
NUMBERS= one two three
+# expect+1: Invalid character ":" in .for loop variable name
.for NUMBERS:M*e in replaced
. info ${NUMBERS} ${NUMBERS:M*e}
.endfor
-# As of 2020-12-31, the name of the iteration variable can contain braces,
-# which gets even more surprising than colons, since it allows to replace
-# sequences of variable expressions. There is no practical use case for
-# this, though.
+# Before for.c 1.173 from 2023-05-08, the name of the iteration variable
+# could contain braces, which allowed to replace sequences of
+# expressions. This possibility was neither intended nor documented.
BASENAME= one
EXT= .c
+# expect+1: Invalid character "}" in .for loop variable name
.for BASENAME}${EXT in replaced
. info ${BASENAME}${EXT}
.endfor
@@ -114,34 +170,41 @@ EXT= .c
i= outer
i2= two
i,= comma
+# expect+2: inner inner inner inner inner
+.for i in inner
+. info $i ${i} ${i:M*} $(i) $(i:M*)
+.endfor
+# expect+2: outer
+.for i in inner
+. info ${i${:U}}
+.endfor
+# expect+2: inner}
+.for i in inner
+. info ${i\}} # XXX: unclear why ForLoop_SubstVarLong needs this
+.endfor
+# expect+2: two comma innerinnerinnerinner
.for i in inner
-. info . $$i: $i
-. info . $${i}: ${i}
-. info . $${i:M*}: ${i:M*}
-. info . $$(i): $(i)
-. info . $$(i:M*): $(i:M*)
-. info . $${i$${:U}}: ${i${:U}}
-. info . $${i\}}: ${i\}} # XXX: unclear why ForLoop_SubstVarLong needs this
-. info . $${i2}: ${i2}
-. info . $${i,}: ${i,}
-. info . adjacent: $i${i}${i:M*}$i
-.endfor
-
-# The variable name can be a single '$' since there is no check on valid
-# variable names. ForLoop_SubstVarShort skips "stupid" variable names though,
-# but ForLoop_SubstVarLong naively parses the body of the loop, substituting
-# each '${$}' with an actual 'dollar'.
+. info ${i2} ${i,} $i${i}${i:M*}$i
+.endfor
+
+# Before for.c 1.173 from 2023-05-08, the variable name could be a single '$'
+# since there was no check on valid variable names. ForLoop_SubstVarShort
+# skipped "stupid" variable names though, but ForLoop_SubstVarLong naively
+# parsed the body of the loop, substituting each '${$}' with an actual
+# '${:Udollar}'.
+# expect+1: Invalid character "$" in .for loop variable name
.for $ in dollar
. info eight $$$$$$$$ and no cents.
. info eight ${$}${$}${$}${$} and no cents.
.endfor
# Outside a .for loop, '${$}' is interpreted differently. The outer '$' starts
-# a variable expression. The inner '$' is followed by a '}' and is thus a
+# an expression. The inner '$' is followed by a '}' and is thus a
# silent syntax error, the '$' is skipped. The variable name is thus '', and
# since since there is never a variable named '', the whole expression '${$}'
# evaluates to an empty string.
closing-brace= } # guard against an
${closing-brace}= <closing-brace> # alternative interpretation
+# expect+1: eight and no cents.
.info eight ${$}${$}${$}${$} and no cents.
# What happens if the values from the .for loop contain a literal newline?
@@ -149,12 +212,17 @@ ${closing-brace}= <closing-brace> # alternative interpretation
# body of the .for loop, where it was then interpreted as a literal newline,
# leading to syntax errors such as "Unclosed variable expression" in the upper
# line and "Invalid line type" in the lower line.
+#
+# The error message occurs in the line of the .for loop since that's the place
+# where the body of the .for loop is constructed, and at this point the
+# newline character gets replaced with a plain space.
+# expect+3: newline in .for value
+# expect+2: newline in .for value
+# expect+2: short: " ", long: " "
.for i in "${.newline}"
-. info short: $i
-. info long: ${i}
+. info short: $i, long: ${i}
.endfor
-
-# No error since the newline character is not actually used.
+# No error since the newline character is not actually used in the body.
.for i in "${.newline}"
.endfor
@@ -164,6 +232,7 @@ ${closing-brace}= <closing-brace> # alternative interpretation
# loop is assembled, and at that point, ForLoop.nextItem had already been
# advanced.
.MAKEFLAGS: -dp
+# expect+1: newline in .for value
.for i in "${.newline}"
: $i
.endfor
@@ -195,6 +264,22 @@ ${closing-brace}= <closing-brace> # alternative interpretation
.for i in ((( {{{ ))) }}}
# $i
.endfor
-.MAKEFLAGS: -d0
-all:
+
+# When generating the body of a .for loop, recognizing the expressions is done
+# using simple heuristics. These can go wrong in ambiguous cases like this.
+# The variable name ',' is unusual as it is not a pronounceable name, but the
+# same principle applies for other names as well. In this case, the text '$,'
+# is replaced with the expression '${:U1}', even though the text does not
+# represent an expression.
+.for , in 1
+# $$i $i
+# VAR= $$i $i ${a:S,from$,to,}
+VAR= $$i $i ${a:S,from$,to,}
+.endfor
+# expect: # $$i $i
+# expect: # VAR= $$i $i ${a:S,from${:U1}to,}
+# expect: VAR= $$i $i ${a:S,from${:U1}to,}
+#
+# When the above variable is evaluated, make will complain about the
+# unfinished modifier ':S', as it is missing a comma.
diff --git a/contrib/bmake/unit-tests/directive-for-generating-endif.exp b/contrib/bmake/unit-tests/directive-for-generating-endif.exp
index 5f57f89c250f..983ea5d6a4cd 100755
--- a/contrib/bmake/unit-tests/directive-for-generating-endif.exp
+++ b/contrib/bmake/unit-tests/directive-for-generating-endif.exp
@@ -1,7 +1,10 @@
-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 26: 3 open conditionals
+make: directive-for-generating-endif.mk:24: if-less endif
+ in .for loop from directive-for-generating-endif.mk:20 with i = 3
+make: directive-for-generating-endif.mk:24: if-less endif
+ in .for loop from directive-for-generating-endif.mk:20 with i = 2
+make: directive-for-generating-endif.mk:24: if-less endif
+ in .for loop from directive-for-generating-endif.mk:20 with i = 1
+make: directive-for-generating-endif.mk:30: 3 open conditionals
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-for-generating-endif.mk b/contrib/bmake/unit-tests/directive-for-generating-endif.mk
index b4d709551003..3079ad3353c2 100755
--- a/contrib/bmake/unit-tests/directive-for-generating-endif.mk
+++ b/contrib/bmake/unit-tests/directive-for-generating-endif.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-for-generating-endif.mk,v 1.1 2020/08/29 18:50:25 rillig Exp $
+# $NetBSD: directive-for-generating-endif.mk,v 1.2 2023/06/01 20:56:35 rillig Exp $
#
# Test whether a .for loop can be used to generate multiple .endif
# directives to close nested .if directives. Depending on the exact
@@ -18,8 +18,12 @@
. if 2
. if 3
.for i in 3 2 1
+# expect+3: if-less endif
+# expect+2: if-less endif
+# expect+1: if-less endif
.endif
.endfor
all:
@:;
+# expect+1: 3 open conditionals
diff --git a/contrib/bmake/unit-tests/directive-for-if.exp b/contrib/bmake/unit-tests/directive-for-if.exp
index 85bfc484856b..0b7ed45eb2a5 100644
--- a/contrib/bmake/unit-tests/directive-for-if.exp
+++ b/contrib/bmake/unit-tests/directive-for-if.exp
@@ -1,8 +1,11 @@
-make: "directive-for-if.mk" line 48: if-less endif
-make: "directive-for-if.mk" line 48: if-less endif
-make: "directive-for-if.mk" line 48: if-less endif
+make: directive-for-if.mk:51: if-less endif
+ in .for loop from directive-for-if.mk:46 with directive = if
+make: directive-for-if.mk:51: if-less endif
+ in .for loop from directive-for-if.mk:46 with directive = ifdef
+make: directive-for-if.mk:51: if-less endif
+ in .for loop from directive-for-if.mk:46 with directive = ifndef
VAR1
VAR3
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "." in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-for-if.mk b/contrib/bmake/unit-tests/directive-for-if.mk
index 8d73e8ae8c4d..f5a20279cc97 100644
--- a/contrib/bmake/unit-tests/directive-for-if.mk
+++ b/contrib/bmake/unit-tests/directive-for-if.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-for-if.mk,v 1.1 2021/08/30 17:08:13 rillig Exp $
+# $NetBSD: directive-for-if.mk,v 1.3 2023/11/19 21:47:52 rillig Exp $
#
# Test for a .for directive that contains an .if directive.
#
@@ -45,6 +45,9 @@
# expanded to their bare textual value.
.for directive in if ifdef ifndef
. ${directive} "1" != "0"
+# expect+3: if-less endif
+# expect+2: if-less endif
+# expect+1: if-less endif
. endif
.endfor
# In 2021, the above code does not generate an error message, even though the
@@ -68,7 +71,7 @@ _!= echo "${var}" 1>&2; echo # In 2005, '.info' was not invented yet.
.endfor
# Before for.c 1.39 from 2008-12-21, a common workaround was to surround the
-# variable expression from the .for loop with '"'. Such a string literal
+# expression from the .for loop with '"'. Such a string literal
# has been allowed since cond.c 1.23 from 2004-04-13. Between that commit and
# the one from 2008, the parser would still get confused if the value from the
# .for loop contained '"', which was effectively a code injection.
diff --git a/contrib/bmake/unit-tests/directive-for-lines.exp b/contrib/bmake/unit-tests/directive-for-lines.exp
index 7aeaaa4a7002..23227122ffd3 100644
--- a/contrib/bmake/unit-tests/directive-for-lines.exp
+++ b/contrib/bmake/unit-tests/directive-for-lines.exp
@@ -1,9 +1,9 @@
-make: "directive-for-lines.mk" line 23: expect 23
-make: "directive-for-lines.mk" line 23: expect 23
-make: "directive-for-lines.mk" line 30: expect 30
-make: "directive-for-lines.mk" line 23: expect 23
-make: "directive-for-lines.mk" line 23: expect 23
-make: "directive-for-lines.mk" line 30: expect 30
+make: directive-for-lines.mk:31: This is line 31.
+make: directive-for-lines.mk:31: This is line 31.
+make: directive-for-lines.mk:38: This is line 38.
+make: directive-for-lines.mk:31: This is line 31.
+make: directive-for-lines.mk:31: This is line 31.
+make: directive-for-lines.mk:38: This is line 38.
make: no target to make.
make: stopped in unit-tests
diff --git a/contrib/bmake/unit-tests/directive-for-lines.mk b/contrib/bmake/unit-tests/directive-for-lines.mk
index 96d659426882..898a1960e76a 100644
--- a/contrib/bmake/unit-tests/directive-for-lines.mk
+++ b/contrib/bmake/unit-tests/directive-for-lines.mk
@@ -1,12 +1,20 @@
-# $NetBSD: directive-for-lines.mk,v 1.3 2020/12/19 12:40:00 rillig Exp $
+# $NetBSD: directive-for-lines.mk,v 1.6 2025/06/30 21:44:39 rillig Exp $
#
# Tests for the line numbers that are reported in .for loops.
#
-# Between 2007-01-01 (git 4d3c468f96e1080e, parse.c 1.127) and 2020-12-19
-# (parse.c 1.494), the line numbers for the .info directives and error
+# Since parse.c 1.127 from 2007-01-01 and before parse.c 1.494 from
+# 2020-12-19, the line numbers for the .info directives and error
# messages inside .for loops had been wrong since ParseGetLine skipped empty
# lines, even when collecting the lines for the .for loop body.
+# expect+21: This is line 31.
+# expect+20: This is line 31.
+# expect+26: This is line 38.
+
+# expect+17: This is line 31.
+# expect+16: This is line 31.
+# expect+22: This is line 38.
+
.for outer in a b
# comment \
@@ -20,13 +28,13 @@
VAR= \
multi-line
-.info expect 23
+.info This is line 31.
.endfor
# comment \
# continued comment
-.info expect 30
+.info This is line 38.
.endfor
diff --git a/contrib/bmake/unit-tests/directive-for-null.exp b/contrib/bmake/unit-tests/directive-for-null.exp
index dee26de25e63..60caebee238f 100644
--- a/contrib/bmake/unit-tests/directive-for-null.exp
+++ b/contrib/bmake/unit-tests/directive-for-null.exp
@@ -1,10 +1,7 @@
-make: "(stdin)" line 2: Zero byte read from file
-make: "(stdin)" line 2: Unexpected end of file in .for loop
-make: "(stdin)" line 3: Zero byte read from file
-make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
-*** Error code 1 (continuing)
+make: (stdin):2: Zero byte read from file
+ in make[1] in directory "<curdir>"
+*** Error code 2 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-for-null.mk b/contrib/bmake/unit-tests/directive-for-null.mk
index a374f508dd55..a833cd1a32e4 100644
--- a/contrib/bmake/unit-tests/directive-for-null.mk
+++ b/contrib/bmake/unit-tests/directive-for-null.mk
@@ -1,19 +1,13 @@
-# $NetBSD: directive-for-null.mk,v 1.1 2020/12/19 16:00:17 rillig Exp $
+# $NetBSD: directive-for-null.mk,v 1.5 2025/03/30 09:51:50 rillig Exp $
#
# Test for parsing a .for loop that accidentally contains a null byte.
#
-# As of 2020-12-19, there are 3 error messages:
-#
-# make: "(stdin)" line 2: Zero byte read from file
-# make: "(stdin)" line 2: Unexpected end of file in for loop.
-# make: "(stdin)" line 3: Zero byte read from file
-#
-# The one about "end of file" might be misleading but is due to the
-# implementation. On both errors and EOF, ParseGetLine returns NULL.
-#
-# The one about the "zero byte" in line 3 is surprising since the only
-# line that contains a null byte is line 2.
+# expect: make: (stdin):2: Zero byte read from file
all: .PHONY
- @printf '%s\n' '.for i in 1 2 3' 'VAR=value' '.endfor' | tr 'l' '\0' \
+ @printf '%s\n' \
+ '.for i in 1 2 3' \
+ 'VAR=value' \
+ '.endfor' \
+ | tr 'l' '\0' \
| ${MAKE} -f -
diff --git a/contrib/bmake/unit-tests/directive-for.exp b/contrib/bmake/unit-tests/directive-for.exp
index dda487917e68..d1c1064f0ef5 100755
--- a/contrib/bmake/unit-tests/directive-for.exp
+++ b/contrib/bmake/unit-tests/directive-for.exp
@@ -1,42 +1,40 @@
-make: "directive-for.mk" line 108: outer
-make: "directive-for.mk" line 133: a:\ a:\file.txt
-make: "directive-for.mk" line 133: d:\\
-make: "directive-for.mk" line 133: d:\\file.txt
-make: "directive-for.mk" line 140: ( ( (
-make: "directive-for.mk" line 140: [ [ [
-make: "directive-for.mk" line 140: { { {
-make: "directive-for.mk" line 140: ) ) )
-make: "directive-for.mk" line 140: ] ] ]
-make: "directive-for.mk" line 140: } } }
-make: "directive-for.mk" line 140: (()) (()) (())
-make: "directive-for.mk" line 140: [[]] [[]] [[]]
-make: "directive-for.mk" line 140: {{}} {{}} {{}}
-make: "directive-for.mk" line 140: )( )( )(
-make: "directive-for.mk" line 140: ][ ][ ][
-make: "directive-for.mk" line 140: }{ }{ }{
-make: "directive-for.mk" line 148: outer value value
-make: "directive-for.mk" line 148: outer "quoted" \"quoted\"
-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
+make: directive-for.mk:117: outer
+make: directive-for.mk:138: a:\ a:\file.txt
+make: directive-for.mk:138: d:\\
+make: directive-for.mk:138: d:\\file.txt
+make: directive-for.mk:158: ( ( (
+make: directive-for.mk:158: [ [ [
+make: directive-for.mk:158: { { {
+make: directive-for.mk:158: ) ) )
+make: directive-for.mk:158: ] ] ]
+make: directive-for.mk:158: } } }
+make: directive-for.mk:158: (()) (()) (())
+make: directive-for.mk:158: [[]] [[]] [[]]
+make: directive-for.mk:158: {{}} {{}} {{}}
+make: directive-for.mk:158: )( )( )(
+make: directive-for.mk:158: ][ ][ ][
+make: directive-for.mk:158: }{ }{ }{
+make: directive-for.mk:166: Invalid character ":" in .for loop variable name
+make: directive-for.mk:173: Invalid character "$" in .for loop variable name
+make: directive-for.mk:185: Invalid character "$" in .for loop variable name
+make: directive-for.mk:208: Missing iteration variables in .for loop
+make: directive-for.mk:234: 1 open conditional
+ in .for loop from directive-for.mk:232 with var = value
+make: directive-for.mk:252: for-less endfor
+make: directive-for.mk:254: if-less endif
+make: directive-for.mk:263: if-less endif
+ in .for loop from directive-for.mk:261 with var = value
+For: new loop 2
+For: end for 2
For: end for 1
-For: loop body:
+For: loop body with outer = o:
.\
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
+For: end for 1
+For: loop body with inner = i:
+make: directive-for.mk:312: newline-item=(a)
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 572c4d6a5c92..72ffaa142ad6 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.13 2022/01/15 12:35:18 rillig Exp $
+# $NetBSD: directive-for.mk,v 1.32 2025/07/01 04:24:20 rillig Exp $
#
# Tests for the .for directive.
#
@@ -8,11 +8,13 @@
# .for _FILE_ in values
# .for .FILE. in values
# .for _f_ in values
-
-# Using the .for loop, lists of values can be produced.
-# In simple cases, the :@var@${var}@ variable modifier can be used to
-# achieve the same effects.
#
+# See also:
+# varmod-loop.mk The ':@var@...@' modifier
+
+# A typical use case for a .for loop is to populate a variable with a list of
+# values depending on other variables. In simple cases, the same effect can
+# be achieved using the ':@var@${var}@' modifier.
.undef NUMBERS
.for num in 1 2 3
NUMBERS+= ${num}
@@ -21,8 +23,9 @@ NUMBERS+= ${num}
. error
.endif
+
# The .for loop also works for multiple iteration variables.
-# This is something that the variable modifier :@ cannot do.
+# This is something that the modifier :@ cannot do as easily.
.for name value in VARNAME value NAME2 value2
${name}= ${value}
.endfor
@@ -30,12 +33,12 @@ ${name}= ${value}
. error
.endif
+
# The .for loop splits the items at whitespace, taking quotes into account,
-# just like the :M or :S variable modifiers.
-#
-# Until 2012-06-03, it had split the items exactly at whitespace, without
-# taking the quotes into account. This had resulted in 10 words.
+# just like the :M or :S modifiers.
#
+# Until 2012-06-03, the .for loop had split the items exactly at whitespace,
+# without taking the quotes into account. This had resulted in 10 words.
.undef WORDS
.for var in one t\ w\ o "three three" 'four four' `five six`
WORDS+= counted
@@ -44,16 +47,19 @@ WORDS+= counted
. error
.endif
+
# In the body of the .for loop, the iteration variables can be accessed
# like normal variables, even though they are not really variables.
#
-# Instead, the expression ${var} is transformed into ${:U1}, ${:U2} and so
-# on, before the loop body is evaluated.
+# Instead, before interpreting the body of the .for loop, the body is
+# generated by replacing each expression ${var} with ${:U1}, ${:U2} and so
+# on.
#
-# A notable effect of this implementation technique is that the .for
+# A noticeable effect of this implementation technique is that the .for
# iteration variables and the normal global variables live in separate
-# namespaces and do not influence each other.
-#
+# namespaces and do not influence each other. The "scope" of the .for loop
+# variables is restricted to the current makefile, it does not reach over to
+# any included makefiles.
var= value before
var2= value before
.for var var2 in 1 2 3 4
@@ -66,9 +72,8 @@ var2= value before
.endif
# Everything from the paragraph above also applies if the loop body is
-# empty, even if there is no actual iteration since the loop items are
-# also empty.
-#
+# empty. In this particular example, the items to be iterated are empty as
+# well.
var= value before
var2= value before
.for var var2 in ${:U}
@@ -80,13 +85,15 @@ var2= value before
. warning After the .for loop, var2 must still have its original value.
.endif
-# Until 2008-12-21, the values of the iteration variables were simply
-# inserted as plain text and then parsed as usual, which made it possible
-# to achieve all kinds of strange effects.
+# Before for.c 1.39 from 2008-12-21, the values of the iteration variables
+# were simply inserted as plain text and then parsed as usual, which made it
+# possible to achieve all kinds of strange effects, such as generating '.if'
+# directives or inserting '$' characters in random places, thereby changing
+# how following '$' are interpreted.
#
-# Before that date, the .for loop expanded to:
+# Before that date, the .for loop below expanded to:
# EXPANSION+= value
-# Since that date, the .for loop expands to:
+# Since that date, the .for loop below expands to:
# EXPANSION${:U+}= value
#
EXPANSION= before
@@ -102,13 +109,16 @@ EXPANSION${plus}= value
.endif
# When the outer .for loop is expanded, it sees the expression ${i} and
-# expands it. The inner loop then has nothing more to expand.
+# expands it. The inner loop then only sees the expression ${:Uouter} and
+# has nothing more to expand.
.for i in outer
. for i in inner
+# expect+1: outer
. info ${i}
. endfor
.endfor
+
# From https://gnats.netbsd.org/29985.
#
# Until 2008-12-21, the .for loop was expanded by replacing the variable
@@ -121,46 +131,83 @@ EXPANSION${plus}= value
# like "a:\ a:\file.txt" that ended in a single backslash. Since then, the
# variable values have been replaced with expressions of the form ${:U...},
# which are not interpreted as code anymore.
-#
-# As of 2020-09-22, a comment in for.c says that it may be possible to
-# produce an "unwanted substitution", but there is no demonstration code yet.
-#
-# The above changes prevent a backslash at the end of a word from being
-# interpreted as part of the code. Because of this, the trailingBackslash
-# hack in Var_Subst is no longer needed and as of 2020-09-22, has been
-# removed.
.for path in a:\ a:\file.txt d:\\ d:\\file.txt
+# expect+3: a:\ a:\file.txt
+# expect+2: d:\\
+# expect+1: d:\\file.txt
. info ${path}
.endfor
+
# Ensure that braces and parentheses are properly escaped by the .for loop.
# Each line must print the same word 3 times.
# See ForLoop_SubstBody.
.for v in ( [ { ) ] } (()) [[]] {{}} )( ][ }{
+# expect+12: ( ( (
+# expect+11: [ [ [
+# expect+10: { { {
+# expect+9: ) ) )
+# expect+8: ] ] ]
+# expect+7: } } }
+# expect+6: (()) (()) (())
+# expect+5: [[]] [[]] [[]]
+# expect+4: {{}} {{}} {{}}
+# expect+3: )( )( )(
+# expect+2: ][ ][ ][
+# expect+1: }{ }{ }{
. info $v ${v} $(v)
.endfor
-# As of 2020-10-25, the variable names may contain arbitrary characters,
-# except for whitespace. This allows for creative side effects. Hopefully
-# nobody is misusing this "feature".
+# Before 2023-05-09, the variable names could contain arbitrary characters,
+# except for whitespace, allowing for creative side effects, as usual for
+# arbitrary code injection.
var= outer
+# expect+1: Invalid character ":" in .for loop variable name
.for var:Q in value "quoted"
-. info ${var} ${var:Q} ${var:Q:Q}
+. info <${var}> <${var:Q}> <${var:Q:Q}>
+.endfor
+
+# Before 2023-05-09, when variable names could contain '$', the short
+# expression '$$' was preserved, the long expressions were substituted.
+# expect+1: Invalid character "$" in .for loop variable name
+.for $ in value
+. info <$$> <${$}> <$($)>
+.endfor
+
+
+# https://gnats.netbsd.org/53146 mentions the idea of using a dynamic
+# variable name in .for loops, based on some other variable. The .for loops
+# are already tricky enough to understand in detail, even without this
+# possibility, therefore the variable names are restricted to using harmless
+# characters only.
+INDIRECT= direct
+# expect+1: Invalid character "$" in .for loop variable name
+.for $(INDIRECT) in value
+# If the variable name could be chosen dynamically, the iteration variable
+# might have been 'direct', thereby expanding the expression '${direct}'.
+. info <$(INDIRECT)> <$(direct)> <$($(INDIRECT))>
.endfor
-# XXX: A parse error or evaluation error in the items of the .for loop
-# should skip the whole loop. As of 2020-12-27, the loop is expanded twice.
-.for var in word1 ${:Uword2:Z} word3
-. info XXX: Not reached ${var}
+# Regular global variables and the "variables" from the .for loop don't
+# interfere with each other. In the following snippet, the variable 'DIRECT'
+# is used both as a global variable, as well as an iteration variable in the
+# .for loop. The expression '${INDIRECT}' refers to the global variable, not
+# to the one from the .for loop.
+DIRECT= global
+INDIRECT= ${DIRECT}
+.for DIRECT in iteration
+. if "${DIRECT} ${INDIRECT}" != "iteration global"
+. error
+. endif
.endfor
# 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
+# expect+1: Missing iteration variables in .for loop
+.for in value
+. error
+.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.
@@ -184,7 +231,8 @@ var= outer
# is processed.
.for var in value
. if 0
-.endfor # expect+0: 1 open conditional
+.endfor
+# expect-1: 1 open conditional
# If there are no iteration values, the loop body is not processed, and the
# check for mismatched conditionals is not performed.
@@ -200,8 +248,10 @@ var= outer
.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
+# expect+1: for-less endfor
+.endfor
+# expect+1: if-less endif
+.endif
# When a .for without the corresponding .endfor occurs in an active branch of
@@ -209,17 +259,25 @@ var= outer
# without looking at any other directives.
.if 1
. for var in value
-. endif # expect+0: if-less endif
+# expect+1: if-less endif
+. 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.
+# Before for.c 1.172 from 2023-05-08, when make parsed a .for loop, it
+# assumed that there was no line continuation between the '.' and the 'for'
+# or 'endfor', as there is no practical reason to break the line at this
+# point.
+#
+# When make scanned the outer .for loop, it did not recognize the inner .for
+# loop as such and instead treated it as an unknown directive. The body of
+# the outer .for loop thus ended above the '.endfor'.
+#
+# When make scanned the inner .for loop, it did not recognize the inner
+# .endfor as such, which led to a parse error 'Unexpected end of file in .for
+# loop' from the '.endfor' line, followed by a second parse error 'for-less
+# .endfor' from the '.\\n endfor' line.
.MAKEFLAGS: -df
.for outer in o
.\
@@ -228,3 +286,28 @@ var= outer
endfor
.endfor
.MAKEFLAGS: -d0
+
+
+# When there is a variable definition 'scope=cmdline' from the command line
+# (which has higher precedence than global variables) and a .for loop iterates
+# over a variable of the same name, the expression '${scope}' expands to the
+# value from the .for loop. This is because when the body of the .for loop is
+# expanded, the expression '${scope}' is textually replaced with ${:Uloop}',
+# without resolving any other variable names (ForLoop_SubstBody). Later, when
+# the body of the .for loop is actually interpreted, the body text doesn't
+# contain the word 'scope' anymore.
+.MAKEFLAGS: scope=cmdline
+.for scope in loop
+. if ${scope} != "loop"
+. error
+. endif
+.endfor
+
+
+# Since at least 1993, iteration stops at the first newline.
+# Back then, the .newline variable didn't exist, therefore it was unlikely
+# that a newline ever occurred.
+.for var in a${.newline}b${.newline}c
+# expect+1: newline-item=(a)
+. info newline-item=(${var})
+.endfor
diff --git a/contrib/bmake/unit-tests/directive-hyphen-include.exp b/contrib/bmake/unit-tests/directive-hyphen-include.exp
index d0835fe464b1..d1d37e783ca9 100755
--- a/contrib/bmake/unit-tests/directive-hyphen-include.exp
+++ b/contrib/bmake/unit-tests/directive-hyphen-include.exp
@@ -1,4 +1,5 @@
-make: "directive-hyphen-include-error.inc" line 1: Invalid line type
+make: directive-hyphen-include-error.inc:1: Invalid line "syntax error"
+ in directive-hyphen-include.mk:20
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" 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 fbfbeb200d4f..de438dfaffac 100755
--- a/contrib/bmake/unit-tests/directive-hyphen-include.mk
+++ b/contrib/bmake/unit-tests/directive-hyphen-include.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-hyphen-include.mk,v 1.2 2022/01/23 21:48:59 rillig Exp $
+# $NetBSD: directive-hyphen-include.mk,v 1.5 2025/06/28 22:39:28 rillig Exp $
#
# Tests for the .-include directive, which includes another file,
# silently skipping it if it cannot be opened.
@@ -15,7 +15,7 @@
.-include "${MAKEFILE}/subdir"
# Errors that are not related to opening the file are still reported.
-# expect: make: "directive-hyphen-include-error.inc" line 1: Invalid line type
+# expect: make: directive-hyphen-include-error.inc:1: Invalid line "syntax error"
_!= echo 'syntax error' > directive-hyphen-include-error.inc
.-include "${.CURDIR}/directive-hyphen-include-error.inc"
_!= rm directive-hyphen-include-error.inc
diff --git a/contrib/bmake/unit-tests/directive-if-nested.exp b/contrib/bmake/unit-tests/directive-if-nested.exp
index 1a9ae02f07b2..06dc1a0300a5 100644
--- a/contrib/bmake/unit-tests/directive-if-nested.exp
+++ b/contrib/bmake/unit-tests/directive-if-nested.exp
@@ -1,2 +1,2 @@
-make: "directive-if-nested.inc" line 1001: deeply nested .if directives
+make: directive-if-nested.inc:1001: deeply nested .if directives
exit status 0
diff --git a/contrib/bmake/unit-tests/directive-if-nested.mk b/contrib/bmake/unit-tests/directive-if-nested.mk
index 19c8e9452660..93fce90b5d52 100644
--- a/contrib/bmake/unit-tests/directive-if-nested.mk
+++ b/contrib/bmake/unit-tests/directive-if-nested.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-if-nested.mk,v 1.1 2020/11/10 22:23:37 rillig Exp $
+# $NetBSD: directive-if-nested.mk,v 1.2 2025/01/03 05:00:37 rillig Exp $
#
# Tests for deeply nested .if directives. By default, memory for 128 nested
# .if directives is pre-allocated, any deeper nesting is reallocated.
@@ -13,7 +13,7 @@ all: set-up test tear-down
set-up: .PHONY
@{ printf '.if %s\n' ${:U:range=1000}; \
printf '.info deeply nested .if directives\n'; \
- printf '.endif # %s\n' ${:U:range=1000}; \
+ printf '.endif # %s\n' ${:U:range=1000:[-1..1]}; \
printf '\n'; \
printf 'all:\n'; \
} > ${GEN}
diff --git a/contrib/bmake/unit-tests/directive-if.exp b/contrib/bmake/unit-tests/directive-if.exp
index 5682df501e9c..634c9ee6b1df 100644
--- a/contrib/bmake/unit-tests/directive-if.exp
+++ b/contrib/bmake/unit-tests/directive-if.exp
@@ -1,18 +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 41: Unknown directive "ifx"
-make: "directive-if.mk" line 43: This is not conditional.
-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: directive-if.mk:14: 0 evaluates to false.
+make: directive-if.mk:19: 1 evaluates to true.
+make: directive-if.mk:43: Unknown directive "ifx"
+make: directive-if.mk:45: This is not conditional.
+make: directive-if.mk:47: if-less else
+make: directive-if.mk:49: This is not conditional.
+make: directive-if.mk:51: if-less endif
+make: directive-if.mk:55: Malformed conditional ""
+make: directive-if.mk:66: Quotes in plain words are probably a mistake.
+make: directive-if.mk:76: Don't do this, always put a space after a directive.
+make: directive-if.mk:81: Don't do this, always put a space after a directive.
+make: directive-if.mk:88: Don't do this, always put a space around comparison operators.
+make: directive-if.mk:95: Don't do this, always put a space after a directive.
+make: directive-if.mk:100: Don't do this, always put a space after a directive.
+make: directive-if.mk:108: 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 1acd5c958008..5d1232d245a9 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.11 2022/01/23 21:48:59 rillig Exp $
+# $NetBSD: directive-if.mk,v 1.14 2025/06/28 22:39:28 rillig Exp $
#
# Tests for the .if directive.
#
@@ -10,10 +10,12 @@
.if 0
. error
.else
+# expect+1: 0 evaluates to false.
. info 0 evaluates to false.
.endif
.if 1
+# expect+1: 1 evaluates to true.
. info 1 evaluates to true.
.else
. error
@@ -49,7 +51,7 @@
.endif
# Missing condition.
-# expect+1: Malformed conditional ()
+# expect+1: Malformed conditional ""
.if
. error
.else
@@ -60,6 +62,7 @@
# though, which are kept. The quotes need not be balanced. The next space
# ends the word, and the remaining " || 1" is parsed as "or true".
.if ${:Uplain"""""} == plain""""" || 1
+# expect+1: Quotes in plain words are probably a mistake.
. info Quotes in plain words are probably a mistake.
# XXX: Accepting quotes in plain words is probably a mistake as well.
.else
@@ -69,26 +72,31 @@
.if0
. error
.else
+# expect+1: Don't do this, always put a space after a directive.
. info Don't do this, always put a space after a directive.
.endif
.if${:U-3}
+# expect+1: Don't do this, always put a space after a directive.
. info Don't do this, always put a space after a directive.
.else
. error
.endif
.if${:U-3}>-4
+# expect+1: Don't do this, always put a space around comparison operators.
. info Don't do this, always put a space around comparison operators.
.else
. error
.endif
.if(1)
+# expect+1: Don't do this, always put a space after a directive.
. info Don't do this, always put a space after a directive.
.endif
.if!0
+# expect+1: Don't do this, always put a space after a directive.
. info Don't do this, always put a space after a directive.
.endif
diff --git a/contrib/bmake/unit-tests/directive-ifmake.exp b/contrib/bmake/unit-tests/directive-ifmake.exp
index bf4ded97911f..09e530d6f13a 100644
--- a/contrib/bmake/unit-tests/directive-ifmake.exp
+++ b/contrib/bmake/unit-tests/directive-ifmake.exp
@@ -1,15 +1,15 @@
-make: "directive-ifmake.mk" line 13: ok: positive condition works
-make: "directive-ifmake.mk" line 24: ok: negation works
-make: "directive-ifmake.mk" line 33: ok: double negation works
-make: "directive-ifmake.mk" line 40: ok: both mentioned
-make: "directive-ifmake.mk" line 47: ok: only those mentioned
-make: "directive-ifmake.mk" line 57: Targets can even be added at parse time.
-make: "directive-ifmake.mk" line 75: ok
+make: directive-ifmake.mk:14: ok: positive condition works
+make: directive-ifmake.mk:26: ok: negation works
+make: directive-ifmake.mk:36: ok: double negation works
+make: directive-ifmake.mk:44: ok: both mentioned
+make: directive-ifmake.mk:52: ok: only those mentioned
+make: directive-ifmake.mk:63: Targets can even be added at parse time.
+make: directive-ifmake.mk:82: ok
: first
: second
: late-target
make: don't know how to make !edge (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "first second late-target !edge" 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 a1ff3aef6825..fa0a56c60030 100644
--- a/contrib/bmake/unit-tests/directive-ifmake.mk
+++ b/contrib/bmake/unit-tests/directive-ifmake.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-ifmake.mk,v 1.10 2022/02/09 21:09:24 rillig Exp $
+# $NetBSD: directive-ifmake.mk,v 1.12 2023/11/19 21:47:52 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.
@@ -10,6 +10,7 @@
# This is the most basic form.
.ifmake first
+# expect+1: ok: positive condition works
. info ok: positive condition works
.else
. warning positive condition fails
@@ -21,6 +22,7 @@
.ifmake !first
. warning unexpected
.else
+# expect+1: ok: negation works
. info ok: negation works
.endif
@@ -30,6 +32,7 @@
# exclamation mark were part of the name instead, the name would be '!!first',
# and such a target was not requested to be made.
.ifmake !!first
+# expect+1: ok: double negation works
. info ok: double negation works
.else
. warning double negation fails
@@ -37,6 +40,7 @@
# Multiple targets can be combined using the && and || operators.
.ifmake first && second
+# expect+1: ok: both mentioned
. info ok: both mentioned
.else
. warning && does not work as expected
@@ -44,6 +48,7 @@
# Negation also works in complex conditions.
.ifmake first && !unmentioned
+# expect+1: ok: only those mentioned
. info ok: only those mentioned
.else
. warning && with ! does not work as expected
@@ -54,6 +59,7 @@
# possible to extend the targets to be made.
.MAKEFLAGS: late-target
.ifmake late-target
+# expect+1: Targets can even be added at parse time.
. info Targets can even be added at parse time.
.else
. info No, targets cannot be added at parse time anymore.
@@ -69,9 +75,10 @@
. error
.endif
-# A condition that consists of a variable expression only (without any
+# A condition that consists of an expression only (without any
# comparison operator) can be used with .if and the other .ifxxx directives.
.ifmake ${:Ufirst}
+# expect+1: ok
. info ok
.else
. error
diff --git a/contrib/bmake/unit-tests/directive-ifndef.exp b/contrib/bmake/unit-tests/directive-ifndef.exp
index c653f6344429..339737268499 100644
--- a/contrib/bmake/unit-tests/directive-ifndef.exp
+++ b/contrib/bmake/unit-tests/directive-ifndef.exp
@@ -1,2 +1,2 @@
-make: "directive-ifndef.mk" line 10: guarded section
+make: directive-ifndef.mk:14: guarded section
exit status 0
diff --git a/contrib/bmake/unit-tests/directive-ifndef.mk b/contrib/bmake/unit-tests/directive-ifndef.mk
index bf509ef8075e..44eec55b4a87 100644
--- a/contrib/bmake/unit-tests/directive-ifndef.mk
+++ b/contrib/bmake/unit-tests/directive-ifndef.mk
@@ -1,12 +1,16 @@
-# $NetBSD: directive-ifndef.mk,v 1.6 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: directive-ifndef.mk,v 1.9 2023/10/19 18:24:33 rillig Exp $
#
# Tests for the .ifndef directive, which can be used for multiple-inclusion
# guards. In contrast to C, where #ifndef and #define nicely line up the
# macro name, there is no such syntax in make. Therefore, it is more
# common to use .if !defined(GUARD) instead.
+#
+# See also:
+# directive-include-guard.mk
.ifndef GUARD
GUARD= # defined
+# expect+1: guarded section
. info guarded section
.endif
@@ -20,5 +24,64 @@ GUARD= # defined
. info guarded section
.endif
+
+# The '.ifndef' directive can be used with multiple arguments, even negating
+# them. Since these conditions are confusing for humans, they should be
+# replaced with easier-to-understand plain '.if' directives.
+DEFINED=
+.ifndef UNDEFINED && UNDEFINED
+.else
+. error
+.endif
+.ifndef UNDEFINED && DEFINED
+. error
+.endif
+.ifndef DEFINED && DEFINED
+. error
+.endif
+.ifndef !UNDEFINED && !UNDEFINED
+. error
+.endif
+.ifndef !UNDEFINED && !DEFINED
+. error
+.endif
+.ifndef !DEFINED && !DEFINED
+.else
+. error
+.endif
+
+
+# The negation from the 'if-not-defined' directive only applies to bare words,
+# but not to numbers, quoted strings or expressions. Those are evaluated
+# without extra negation, just like in a plain '.if' directive.
+.ifndef 0
+. error
+.endif
+.ifndef 1
+.else
+. error
+.endif
+.ifndef ""
+. error
+.endif
+.ifndef "word"
+.else
+. error
+.endif
+.ifndef ${:UUNDEFINED}
+.else
+. error
+.endif
+.ifndef ${:UDEFINED}
+. error
+.endif
+.ifndef ${:U0}
+. error
+.endif
+.ifndef ${:U1}
+.else
+. error
+.endif
+
+
all:
- @:;
diff --git a/contrib/bmake/unit-tests/directive-include-fatal.exp b/contrib/bmake/unit-tests/directive-include-fatal.exp
index c8ca97a0fd5f..abe28141ff29 100755
--- a/contrib/bmake/unit-tests/directive-include-fatal.exp
+++ b/contrib/bmake/unit-tests/directive-include-fatal.exp
@@ -1,4 +1,4 @@
-make: "directive-include-fatal.mk" line 13: Malformed conditional (${UNDEF})
+make: directive-include-fatal.mk:14: Variable "UNDEF" is undefined
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-include-fatal.mk b/contrib/bmake/unit-tests/directive-include-fatal.mk
index 6744f9e80e5c..ecda6f1982c4 100755
--- a/contrib/bmake/unit-tests/directive-include-fatal.mk
+++ b/contrib/bmake/unit-tests/directive-include-fatal.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-include-fatal.mk,v 1.3 2021/02/01 22:16:57 rillig Exp $
+# $NetBSD: directive-include-fatal.mk,v 1.6 2025/01/11 21:21:33 rillig Exp $
#
# Test for the .include directive combined with fatal errors.
#
@@ -10,6 +10,7 @@
# the "fatals" counter.
# Using an undefined variable in a condition generates a fatal error.
+# expect+1: Variable "UNDEF" is undefined
.if ${UNDEF}
.endif
diff --git a/contrib/bmake/unit-tests/directive-include-guard.exp b/contrib/bmake/unit-tests/directive-include-guard.exp
new file mode 100644
index 000000000000..264ae11cde8b
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-include-guard.exp
@@ -0,0 +1,106 @@
+Parse_PushInput: variable-ifndef.tmp:1
+Skipping 'variable-ifndef.tmp' because 'VARIABLE_IFNDEF' is defined
+Parse_PushInput: variable-ifndef-reuse.tmp:1
+Skipping 'variable-ifndef-reuse.tmp' because 'VARIABLE_IFNDEF' is defined
+Parse_PushInput: variable-ifndef-zero.tmp:1
+Parse_PushInput: variable-ifndef-zero.tmp:1
+Parse_PushInput: variable-ifndef-one.tmp:1
+Parse_PushInput: variable-ifndef-one.tmp:1
+Parse_PushInput: comments.tmp:1
+Skipping 'comments.tmp' because 'COMMENTS' is defined
+Parse_PushInput: variable-if.tmp:1
+Skipping 'variable-if.tmp' because 'VARIABLE_IF' is defined
+Parse_PushInput: variable-if-reuse.tmp:1
+Skipping 'variable-if-reuse.tmp' because 'VARIABLE_IF' is defined
+Parse_PushInput: variable-if-triple-negation.tmp:1
+Parse_PushInput: variable-if-triple-negation.tmp:1
+Parse_PushInput: variable-if-spaced.tmp:1
+Parse_PushInput: variable-if-spaced.tmp:1
+Parse_PushInput: variable-if-parenthesized.tmp:1
+Parse_PushInput: variable-if-parenthesized.tmp:1
+Parse_PushInput: variable-ifdef-negated.tmp:1
+Parse_PushInput: variable-ifdef-negated.tmp:1
+Parse_PushInput: variable-name-mismatch.tmp:1
+Parse_PushInput: variable-name-mismatch.tmp:1
+Parse_PushInput: variable-ifndef-parenthesized.tmp:1
+Parse_PushInput: variable-ifndef-parenthesized.tmp:1
+Parse_PushInput: variable-name-exclamation.tmp:1
+Parse_PushInput: variable-name-exclamation.tmp:1
+Parse_PushInput: variable-name-exclamation-middle.tmp:1
+Parse_PushInput: variable-name-exclamation-middle.tmp:1
+Parse_PushInput: variable-name-parentheses.tmp:1
+Parse_PushInput: variable-name-parentheses.tmp:1
+Parse_PushInput: variable-ifndef-plus.tmp:1
+Parse_PushInput: variable-ifndef-plus.tmp:1
+Parse_PushInput: variable-if-plus.tmp:1
+Parse_PushInput: variable-if-plus.tmp:1
+Parse_PushInput: variable-ifndef-indirect.tmp:1
+Parse_PushInput: variable-ifndef-indirect.tmp:1
+Parse_PushInput: variable-if-indirect.tmp:1
+Parse_PushInput: variable-if-indirect.tmp:1
+Parse_PushInput: variable-assign-indirect.tmp:1
+Skipping 'variable-assign-indirect.tmp' because 'VARIABLE_ASSIGN_INDIRECT' is defined
+Parse_PushInput: variable-assign-late.tmp:1
+Skipping 'variable-assign-late.tmp' because 'VARIABLE_ASSIGN_LATE' is defined
+Parse_PushInput: variable-assign-nested.tmp:1
+Parse_PushInput: .for loop in variable-assign-nested.tmp:3
+Skipping 'variable-assign-nested.tmp' because 'VARIABLE_ASSIGN_NESTED' is defined
+Parse_PushInput: variable-already-defined.tmp:1
+Skipping 'variable-already-defined.tmp' because 'VARIABLE_ALREADY_DEFINED' is defined
+Parse_PushInput: variable-defined-then-undefined.tmp:1
+Parse_PushInput: variable-defined-then-undefined.tmp:1
+Parse_PushInput: variable-two-times.tmp:1
+Parse_PushInput: variable-two-times.tmp:1
+Parse_PushInput: variable-clash.tmp:1
+Skipping 'variable-clash.tmp' because 'VARIABLE_IF' is defined
+Parse_PushInput: variable-swapped.tmp:1
+Parse_PushInput: variable-swapped.tmp:1
+Parse_PushInput: variable-undef-between.tmp:1
+Parse_PushInput: variable-undef-between.tmp:1
+Parse_PushInput: variable-undef-inside.tmp:1
+Parse_PushInput: variable-undef-inside.tmp:1
+Parse_PushInput: variable-not-defined.tmp:1
+Parse_PushInput: variable-not-defined.tmp:1
+Parse_PushInput: elif.tmp:1
+Parse_PushInput: elif.tmp:1
+Parse_PushInput: elif-reuse.tmp:1
+Parse_PushInput: elif-reuse.tmp:1
+Parse_PushInput: else.tmp:1
+Parse_PushInput: else.tmp:1
+Parse_PushInput: else-reuse.tmp:1
+Parse_PushInput: else-reuse.tmp:1
+Parse_PushInput: inner-if-elif-else.tmp:1
+Skipping 'inner-if-elif-else.tmp' because 'INNER_IF_ELIF_ELSE' is defined
+Parse_PushInput: target.tmp:1
+Skipping 'target.tmp' because '__target.tmp__' is defined
+Parse_PushInput: target-sys.tmp:1
+Skipping 'target-sys.tmp' because '__<target-sys.tmp>__' is defined
+Parse_PushInput: target-indirect.tmp:1
+Skipping 'target-indirect.tmp' because 'target-indirect.tmp' is defined
+Parse_PushInput: target-indirect-PARSEFILE.tmp:1
+Skipping 'target-indirect-PARSEFILE.tmp' because '__target-indirect-PARSEFILE.tmp__' is defined
+Parse_PushInput: target-indirect-PARSEFILE2.tmp:1
+Skipping 'target-indirect-PARSEFILE2.tmp' because '__target-indirect-PARSEFILE2.tmp__' is defined
+Parse_PushInput: subdir/target-indirect-PARSEFILE.tmp:1
+Skipping 'subdir/target-indirect-PARSEFILE.tmp' because '__target-indirect-PARSEFILE.tmp__' is defined
+Parse_PushInput: target-indirect-PARSEDIR-PARSEFILE.tmp:1
+Skipping 'target-indirect-PARSEDIR-PARSEFILE.tmp' because '__target-indirect-PARSEDIR-PARSEFILE.tmp__' is defined
+Parse_PushInput: subdir/target-indirect-PARSEDIR-PARSEFILE.tmp:1
+Skipping 'subdir/target-indirect-PARSEDIR-PARSEFILE.tmp' because '__subdir/target-indirect-PARSEDIR-PARSEFILE.tmp__' is defined
+Parse_PushInput: target-unguarded.tmp:1
+Parse_PushInput: target-unguarded.tmp:1
+Parse_PushInput: target-plus.tmp:1
+Parse_PushInput: target-plus.tmp:1
+Parse_PushInput: target-already-defined.tmp:1
+Skipping 'target-already-defined.tmp' because 'target-already-defined' is defined
+Parse_PushInput: target-name-exclamation.tmp:1
+Parse_PushInput: target-name-exclamation.tmp:1
+Parse_PushInput: target-name-leading-space.tmp:1
+Parse_PushInput: target-name-leading-space.tmp:1
+Parse_PushInput: target-name-trailing-space.tmp:1
+Parse_PushInput: target-name-trailing-space.tmp:1
+Parse_PushInput: target-call-parenthesized.tmp:1
+Parse_PushInput: target-call-parenthesized.tmp:1
+Parse_PushInput: multiline.tmp:1
+Skipping 'multiline.tmp' because 'MULTILINE' is defined
+exit status 0
diff --git a/contrib/bmake/unit-tests/directive-include-guard.mk b/contrib/bmake/unit-tests/directive-include-guard.mk
new file mode 100644
index 000000000000..702d0f4ab9dc
--- /dev/null
+++ b/contrib/bmake/unit-tests/directive-include-guard.mk
@@ -0,0 +1,648 @@
+# $NetBSD: directive-include-guard.mk,v 1.19 2025/04/11 17:21:31 rillig Exp $
+#
+# Tests for multiple-inclusion guards in makefiles.
+#
+# A file that is guarded by a multiple-inclusion guard has one of the
+# following forms:
+#
+# .ifndef GUARD_VARIABLE
+# .endif
+#
+# .if !defined(GUARD_VARIABLE)
+# .endif
+#
+# .if !target(guard-target)
+# .endif
+#
+# When such a file is included for the second or later time, and the guard
+# variable or the guard target is defined, the file is skipped completely, as
+# including it would not have any effect, not even on the special variable
+# '.MAKE.MAKEFILES', as that variable skips duplicate pathnames.
+#
+# See also:
+# https://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html
+
+# Each of the following test cases creates a temporary file named after the
+# test case and writes some lines of text to that file. That file is then
+# included twice, to see whether the second '.include' is skipped.
+
+
+# This is the canonical form of a variable-based multiple-inclusion guard.
+CASES+= variable-ifndef
+LINES.variable-ifndef= \
+ '.ifndef VARIABLE_IFNDEF' \
+ 'VARIABLE_IFNDEF=' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifndef.tmp:1
+# expect: Skipping 'variable-ifndef.tmp' because 'VARIABLE_IFNDEF' is defined
+
+# A file that reuses a guard from a previous file (or whose guard is defined
+# for any other reason) is only processed once, to see whether it is guarded.
+# Its content is skipped, therefore the syntax error is not detected.
+CASES+= variable-ifndef-reuse
+LINES.variable-ifndef-reuse= \
+ '.ifndef VARIABLE_IFNDEF' \
+ 'syntax error' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifndef-reuse.tmp:1
+# expect: Skipping 'variable-ifndef-reuse.tmp' because 'VARIABLE_IFNDEF' is defined
+
+# The guard variable cannot be a number, as numbers are interpreted
+# differently from bare words.
+CASES+= variable-ifndef-zero
+LINES.variable-ifndef-zero= \
+ '.ifndef 0e0' \
+ 'syntax error' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifndef-zero.tmp:1
+# expect: Parse_PushInput: variable-ifndef-zero.tmp:1
+
+# The guard variable cannot be a number, as numbers are interpreted
+# differently from bare words.
+CASES+= variable-ifndef-one
+LINES.variable-ifndef-one= \
+ '.ifndef 1' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifndef-one.tmp:1
+# expect: Parse_PushInput: variable-ifndef-one.tmp:1
+
+# Comments and empty lines do not affect the multiple-inclusion guard.
+CASES+= comments
+LINES.comments= \
+ '\# comment' \
+ '' \
+ '.ifndef COMMENTS' \
+ '\# comment' \
+ 'COMMENTS=\#comment' \
+ '.endif' \
+ '\# comment'
+# expect: Parse_PushInput: comments.tmp:1
+# expect: Skipping 'comments.tmp' because 'COMMENTS' is defined
+
+# An alternative form uses the 'defined' function. It is more verbose than
+# the canonical form but avoids the '.ifndef' directive, as that directive is
+# not commonly used.
+CASES+= variable-if
+LINES.variable-if= \
+ '.if !defined(VARIABLE_IF)' \
+ 'VARIABLE_IF=' \
+ '.endif'
+# expect: Parse_PushInput: variable-if.tmp:1
+# expect: Skipping 'variable-if.tmp' because 'VARIABLE_IF' is defined
+
+# A file that reuses a guard from a previous file (or whose guard is defined
+# for any other reason) is only processed once, to see whether it is guarded.
+# Its content is skipped, therefore the syntax error is not detected.
+CASES+= variable-if-reuse
+LINES.variable-if-reuse= \
+ '.if !defined(VARIABLE_IF)' \
+ 'syntax error' \
+ '.endif'
+# expect: Parse_PushInput: variable-if-reuse.tmp:1
+# expect: Skipping 'variable-if-reuse.tmp' because 'VARIABLE_IF' is defined
+
+# Triple negation is so uncommon that it's not recognized, even though it has
+# the same effect as a single negation.
+CASES+= variable-if-triple-negation
+LINES.variable-if-triple-negation= \
+ '.if !!!defined(VARIABLE_IF_TRIPLE_NEGATION)' \
+ 'VARIABLE_IF_TRIPLE_NEGATION=' \
+ '.endif'
+# expect: Parse_PushInput: variable-if-triple-negation.tmp:1
+# expect: Parse_PushInput: variable-if-triple-negation.tmp:1
+
+# If the guard variable is enclosed in spaces, it does not have an effect, as
+# that form is not common in practice.
+CASES+= variable-if-spaced
+LINES.variable-if-spaced= \
+ '.if !defined( VARIABLE_IF_SPACED )' \
+ 'VARIABLE_IF_SPACED=' \
+ '.endif'
+# expect: Parse_PushInput: variable-if-spaced.tmp:1
+# expect: Parse_PushInput: variable-if-spaced.tmp:1
+
+# If the guard variable condition is enclosed in parentheses, it does not have
+# an effect, as that form is not common in practice.
+CASES+= variable-if-parenthesized
+LINES.variable-if-parenthesized= \
+ '.if (!defined(VARIABLE_IF_PARENTHESIZED))' \
+ 'VARIABLE_IF_PARENTHESIZED=' \
+ '.endif'
+# expect: Parse_PushInput: variable-if-parenthesized.tmp:1
+# expect: Parse_PushInput: variable-if-parenthesized.tmp:1
+
+# A conditional other than '.if' or '.ifndef' does not guard the file, even if
+# it is otherwise equivalent to the above accepted forms.
+CASES+= variable-ifdef-negated
+LINES.variable-ifdef-negated= \
+ '.ifdef !VARIABLE_IFDEF_NEGATED' \
+ 'VARIABLE_IFDEF_NEGATED=' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifdef-negated.tmp:1
+# expect: Parse_PushInput: variable-ifdef-negated.tmp:1
+
+# The variable names in the '.if' and the assignment must be the same.
+CASES+= variable-name-mismatch
+LINES.variable-name-mismatch= \
+ '.ifndef VARIABLE_NAME_MISMATCH' \
+ 'VARIABLE_NAME_DIFFERENT=' \
+ '.endif'
+# expect: Parse_PushInput: variable-name-mismatch.tmp:1
+# expect: Parse_PushInput: variable-name-mismatch.tmp:1
+
+# If the guard variable condition is enclosed in parentheses, it does not have
+# an effect, as that form is not common in practice.
+CASES+= variable-ifndef-parenthesized
+LINES.variable-ifndef-parenthesized= \
+ '.ifndef (VARIABLE_IFNDEF_PARENTHESIZED)' \
+ 'VARIABLE_IFNDEF_PARENTHESIZED=' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifndef-parenthesized.tmp:1
+# expect: Parse_PushInput: variable-ifndef-parenthesized.tmp:1
+
+# The variable name '!VARNAME' cannot be used in an '.ifndef' directive, as
+# the '!' would be a negation. It is syntactically valid in a '.if !defined'
+# condition, but this case is so uncommon that the guard mechanism doesn't
+# accept '!' in the guard variable name. Furthermore, when defining the
+# variable, the character '!' has to be escaped, to prevent it from being
+# interpreted as the '!' dependency operator.
+CASES+= variable-name-exclamation
+LINES.variable-name-exclamation= \
+ '.if !defined(!VARIABLE_NAME_EXCLAMATION)' \
+ '${:U!}VARIABLE_NAME_EXCLAMATION=' \
+ '.endif'
+# expect: Parse_PushInput: variable-name-exclamation.tmp:1
+# expect: Parse_PushInput: variable-name-exclamation.tmp:1
+
+# In general, a variable name can contain a '!' in the middle, as that
+# character is interpreted as an ordinary character in conditions as well as
+# on the left side of a variable assignment. For guard variable names, the
+# '!' is not supported in any place, though.
+CASES+= variable-name-exclamation-middle
+LINES.variable-name-exclamation-middle= \
+ '.ifndef VARIABLE_NAME!MIDDLE' \
+ 'VARIABLE_NAME!MIDDLE=' \
+ '.endif'
+# expect: Parse_PushInput: variable-name-exclamation-middle.tmp:1
+# expect: Parse_PushInput: variable-name-exclamation-middle.tmp:1
+
+# A variable name can contain balanced parentheses, at least in conditions and
+# on the left side of a variable assignment. There are enough places in make
+# where parentheses or braces are handled inconsistently to make this naming
+# choice a bad idea, therefore these characters are not allowed in guard
+# variable names.
+CASES+= variable-name-parentheses
+LINES.variable-name-parentheses= \
+ '.ifndef VARIABLE_NAME(&)PARENTHESES' \
+ 'VARIABLE_NAME(&)PARENTHESES=' \
+ '.endif'
+# expect: Parse_PushInput: variable-name-parentheses.tmp:1
+# expect: Parse_PushInput: variable-name-parentheses.tmp:1
+
+# The guard condition must consist of only the guard variable, nothing else.
+CASES+= variable-ifndef-plus
+LINES.variable-ifndef-plus= \
+ '.ifndef VARIABLE_IFNDEF_PLUS && VARIABLE_IFNDEF_SECOND' \
+ 'VARIABLE_IFNDEF_PLUS=' \
+ 'VARIABLE_IFNDEF_SECOND=' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifndef-plus.tmp:1
+# expect: Parse_PushInput: variable-ifndef-plus.tmp:1
+
+# The guard condition must consist of only the guard variable, nothing else.
+CASES+= variable-if-plus
+LINES.variable-if-plus= \
+ '.if !defined(VARIABLE_IF_PLUS) && !defined(VARIABLE_IF_SECOND)' \
+ 'VARIABLE_IF_PLUS=' \
+ 'VARIABLE_IF_SECOND=' \
+ '.endif'
+# expect: Parse_PushInput: variable-if-plus.tmp:1
+# expect: Parse_PushInput: variable-if-plus.tmp:1
+
+# The variable name in an '.ifndef' guard must be given directly, it must not
+# contain any '$' expression.
+CASES+= variable-ifndef-indirect
+LINES.variable-ifndef-indirect= \
+ '.ifndef $${VARIABLE_IFNDEF_INDIRECT:L}' \
+ 'VARIABLE_IFNDEF_INDIRECT=' \
+ '.endif'
+# expect: Parse_PushInput: variable-ifndef-indirect.tmp:1
+# expect: Parse_PushInput: variable-ifndef-indirect.tmp:1
+
+# The variable name in an '.if' guard must be given directly, it must not
+# contain any '$' expression.
+CASES+= variable-if-indirect
+LINES.variable-if-indirect= \
+ '.if !defined($${VARIABLE_IF_INDIRECT:L})' \
+ 'VARIABLE_IF_INDIRECT=' \
+ '.endif'
+# expect: Parse_PushInput: variable-if-indirect.tmp:1
+# expect: Parse_PushInput: variable-if-indirect.tmp:1
+
+# The variable name in the guard condition must only contain alphanumeric
+# characters and underscores. The place where the guard variable is defined
+# is more flexible, as long as the variable is defined at the point where the
+# file is included the next time.
+CASES+= variable-assign-indirect
+LINES.variable-assign-indirect= \
+ '.ifndef VARIABLE_ASSIGN_INDIRECT' \
+ '$${VARIABLE_ASSIGN_INDIRECT:L}=' \
+ '.endif'
+# expect: Parse_PushInput: variable-assign-indirect.tmp:1
+# expect: Skipping 'variable-assign-indirect.tmp' because 'VARIABLE_ASSIGN_INDIRECT' is defined
+
+# The time at which the guard variable is defined doesn't matter, as long as
+# it is defined at the point where the file is included the next time.
+CASES+= variable-assign-late
+LINES.variable-assign-late= \
+ '.ifndef VARIABLE_ASSIGN_LATE' \
+ 'VARIABLE_ASSIGN_LATE_OTHER=' \
+ 'VARIABLE_ASSIGN_LATE=' \
+ '.endif'
+# expect: Parse_PushInput: variable-assign-late.tmp:1
+# expect: Skipping 'variable-assign-late.tmp' because 'VARIABLE_ASSIGN_LATE' is defined
+
+# The time at which the guard variable is defined doesn't matter, as long as
+# it is defined at the point where the file is included the next time.
+CASES+= variable-assign-nested
+LINES.variable-assign-nested= \
+ '.ifndef VARIABLE_ASSIGN_NESTED' \
+ '. if 1' \
+ '. for i in once' \
+ 'VARIABLE_ASSIGN_NESTED=' \
+ '. endfor' \
+ '. endif' \
+ '.endif'
+# expect: Parse_PushInput: variable-assign-nested.tmp:1
+# expect: Skipping 'variable-assign-nested.tmp' because 'VARIABLE_ASSIGN_NESTED' is defined
+
+# If the guard variable is defined before the file is included for the first
+# time, the file is considered guarded as well. In such a case, the parser
+# skips almost all lines, as they are irrelevant, but the structure of the
+# top-level '.if/.endif' conditional can be determined reliably enough to
+# decide whether the file is guarded.
+CASES+= variable-already-defined
+LINES.variable-already-defined= \
+ '.ifndef VARIABLE_ALREADY_DEFINED' \
+ 'VARIABLE_ALREADY_DEFINED=' \
+ '.endif'
+VARIABLE_ALREADY_DEFINED=
+# expect: Parse_PushInput: variable-already-defined.tmp:1
+# expect: Skipping 'variable-already-defined.tmp' because 'VARIABLE_ALREADY_DEFINED' is defined
+
+# If the guard variable is defined before the file is included the first time,
+# the file is processed but its content is skipped. If that same guard
+# variable is undefined when the file is included the second time, the file is
+# processed as usual.
+CASES+= variable-defined-then-undefined
+LINES.variable-defined-then-undefined= \
+ '.ifndef VARIABLE_DEFINED_THEN_UNDEFINED' \
+ '.endif'
+VARIABLE_DEFINED_THEN_UNDEFINED=
+UNDEF_BETWEEN.variable-defined-then-undefined= \
+ VARIABLE_DEFINED_THEN_UNDEFINED
+# expect: Parse_PushInput: variable-defined-then-undefined.tmp:1
+# expect: Parse_PushInput: variable-defined-then-undefined.tmp:1
+
+# The whole file content must be guarded by a single '.if' conditional, not by
+# several, as each of these conditionals would require its separate guard.
+# This case is not expected to occur in practice, as the two parts would
+# rather be split into separate files.
+CASES+= variable-two-times
+LINES.variable-two-times= \
+ '.ifndef VARIABLE_TWO_TIMES_1' \
+ 'VARIABLE_TWO_TIMES_1=' \
+ '.endif' \
+ '.ifndef VARIABLE_TWO_TIMES_2' \
+ 'VARIABLE_TWO_TIMES_2=' \
+ '.endif'
+# expect: Parse_PushInput: variable-two-times.tmp:1
+# expect: Parse_PushInput: variable-two-times.tmp:1
+
+# When multiple files use the same guard variable name, the optimization of
+# skipping the file affects each of these files.
+#
+# Choosing unique guard names is the responsibility of the makefile authors.
+# A typical pattern of guard variable names is '${PROJECT}_${DIR}_${FILE}_MK'.
+# System-provided files typically start the guard names with '_'.
+CASES+= variable-clash
+LINES.variable-clash= \
+ ${LINES.variable-if}
+# expect: Parse_PushInput: variable-clash.tmp:1
+# expect: Skipping 'variable-clash.tmp' because 'VARIABLE_IF' is defined
+
+# The conditional must come before the assignment, otherwise the conditional
+# is useless, as it always evaluates to false.
+CASES+= variable-swapped
+LINES.variable-swapped= \
+ 'SWAPPED=' \
+ '.ifndef SWAPPED' \
+ '. error' \
+ '.endif'
+# expect: Parse_PushInput: variable-swapped.tmp:1
+# expect: Parse_PushInput: variable-swapped.tmp:1
+
+# If the guard variable is undefined between the first and the second time the
+# file is included, the guarded file is included again.
+CASES+= variable-undef-between
+LINES.variable-undef-between= \
+ '.ifndef VARIABLE_UNDEF_BETWEEN' \
+ 'VARIABLE_UNDEF_BETWEEN=' \
+ '.endif'
+UNDEF_BETWEEN.variable-undef-between= \
+ VARIABLE_UNDEF_BETWEEN
+# expect: Parse_PushInput: variable-undef-between.tmp:1
+# expect: Parse_PushInput: variable-undef-between.tmp:1
+
+# If the guard variable is undefined while the file is included the first
+# time, the guard does not have an effect, and the file is included again.
+CASES+= variable-undef-inside
+LINES.variable-undef-inside= \
+ '.ifndef VARIABLE_UNDEF_INSIDE' \
+ 'VARIABLE_UNDEF_INSIDE=' \
+ '.undef VARIABLE_UNDEF_INSIDE' \
+ '.endif'
+# expect: Parse_PushInput: variable-undef-inside.tmp:1
+# expect: Parse_PushInput: variable-undef-inside.tmp:1
+
+# If the file does not define the guard variable, the guard does not have an
+# effect, and the file is included again.
+CASES+= variable-not-defined
+LINES.variable-not-defined= \
+ '.ifndef VARIABLE_NOT_DEFINED' \
+ '.endif'
+# expect: Parse_PushInput: variable-not-defined.tmp:1
+# expect: Parse_PushInput: variable-not-defined.tmp:1
+
+# The outermost '.if' must not have an '.elif' branch.
+CASES+= elif
+LINES.elif= \
+ '.ifndef ELIF' \
+ 'ELIF=' \
+ '.elif 1' \
+ '.endif'
+# expect: Parse_PushInput: elif.tmp:1
+# expect: Parse_PushInput: elif.tmp:1
+
+# When a file with an '.if/.elif/.endif' conditional at the top level is
+# included, it is never optimized, as one of its branches is taken.
+CASES+= elif-reuse
+LINES.elif-reuse= \
+ '.ifndef ELIF' \
+ 'syntax error' \
+ '.elif 1' \
+ '.endif'
+# expect: Parse_PushInput: elif-reuse.tmp:1
+# expect: Parse_PushInput: elif-reuse.tmp:1
+
+# The outermost '.if' must not have an '.else' branch.
+CASES+= else
+LINES.else= \
+ '.ifndef ELSE' \
+ 'ELSE=' \
+ '.else' \
+ '.endif'
+# expect: Parse_PushInput: else.tmp:1
+# expect: Parse_PushInput: else.tmp:1
+
+# When a file with an '.if/.else/.endif' conditional at the top level is
+# included, it is never optimized, as one of its branches is taken.
+CASES+= else-reuse
+LINES.else-reuse= \
+ '.ifndef ELSE' \
+ 'syntax error' \
+ '.else' \
+ '.endif'
+# expect: Parse_PushInput: else-reuse.tmp:1
+# expect: Parse_PushInput: else-reuse.tmp:1
+
+# The inner '.if' directives may have an '.elif' or '.else', and it doesn't
+# matter which of their branches are taken.
+CASES+= inner-if-elif-else
+LINES.inner-if-elif-else= \
+ '.ifndef INNER_IF_ELIF_ELSE' \
+ 'INNER_IF_ELIF_ELSE=' \
+ '. if 0' \
+ '. elif 0' \
+ '. else' \
+ '. endif' \
+ '. if 0' \
+ '. elif 1' \
+ '. else' \
+ '. endif' \
+ '. if 1' \
+ '. elif 1' \
+ '. else' \
+ '. endif' \
+ '.endif'
+# expect: Parse_PushInput: inner-if-elif-else.tmp:1
+# expect: Skipping 'inner-if-elif-else.tmp' because 'INNER_IF_ELIF_ELSE' is defined
+
+# The guard can also be a target instead of a variable. Using a target as a
+# guard has the benefit that a target cannot be undefined once it is defined.
+# The target should be declared '.NOTMAIN'. Since the target names are
+# usually chosen according to a pattern that doesn't interfere with real
+# target names, they don't need to be declared '.PHONY' as they don't generate
+# filesystem operations.
+CASES+= target
+LINES.target= \
+ '.if !target(__target.tmp__)' \
+ '__target.tmp__: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target.tmp:1
+# expect: Skipping 'target.tmp' because '__target.tmp__' is defined
+
+# When used for system files, the target name may include '<' and '>', for
+# symmetry with the '.include <sys.mk>' directive. The characters '<' and '>'
+# are ordinary characters.
+CASES+= target-sys
+LINES.target-sys= \
+ '.if !target(__<target-sys.tmp>__)' \
+ '__<target-sys.tmp>__: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-sys.tmp:1
+# expect: Skipping 'target-sys.tmp' because '__<target-sys.tmp>__' is defined
+
+# The target name may include variable references. These references are
+# expanded as usual. Due to the current implementation, the expressions are
+# evaluated twice: Once for checking whether the condition evaluates to true,
+# and once for determining the guard name. This double evaluation should not
+# matter in practice, as guard expressions are expected to be simple,
+# deterministic and without side effects.
+CASES+= target-indirect
+LINES.target-indirect= \
+ '.if !target($${target-indirect.tmp:L})' \
+ 'target-indirect.tmp: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-indirect.tmp:1
+# expect: Skipping 'target-indirect.tmp' because 'target-indirect.tmp' is defined
+
+# A common form of guard target is __${.PARSEFILE}__. This form can only be
+# used if all files using this form have unique basenames. To get a robust
+# pattern based on the same idea, use __${.PARSEDIR}/${.PARSEFILE}__ instead.
+# This form does not work when the basename contains whitespace characters, as
+# it is not possible to define a target with whitespace, not even by cheating.
+CASES+= target-indirect-PARSEFILE
+LINES.target-indirect-PARSEFILE= \
+ '.if !target(__$${.PARSEFILE}__)' \
+ '__$${.PARSEFILE}__: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-indirect-PARSEFILE.tmp:1
+# expect: Skipping 'target-indirect-PARSEFILE.tmp' because '__target-indirect-PARSEFILE.tmp__' is defined
+
+# Two files with different basenames can both use the same syntactic pattern
+# for the target guard name, as the expressions expand to different strings.
+CASES+= target-indirect-PARSEFILE2
+LINES.target-indirect-PARSEFILE2= \
+ '.if !target(__$${.PARSEFILE}__)' \
+ '__$${.PARSEFILE}__: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-indirect-PARSEFILE2.tmp:1
+# expect: Skipping 'target-indirect-PARSEFILE2.tmp' because '__target-indirect-PARSEFILE2.tmp__' is defined
+
+# Using plain .PARSEFILE without .PARSEDIR leads to name clashes. The include
+# guard is the same as in the test case 'target-indirect-PARSEFILE', as the
+# guard name only contains the basename but not the directory name. So even
+# without defining the guard target, the file is considered guarded.
+CASES+= subdir/target-indirect-PARSEFILE
+LINES.subdir/target-indirect-PARSEFILE= \
+ '.if !target(__$${.PARSEFILE}__)' \
+ '.endif'
+# expect: Parse_PushInput: subdir/target-indirect-PARSEFILE.tmp:1
+# expect: Skipping 'subdir/target-indirect-PARSEFILE.tmp' because '__target-indirect-PARSEFILE.tmp__' is defined
+
+# Another common form of guard target is __${.PARSEDIR}/${.PARSEFILE}__
+# or __${.PARSEDIR:tA}/${.PARSEFILE}__ to be truly unique.
+CASES+= target-indirect-PARSEDIR-PARSEFILE
+LINES.target-indirect-PARSEDIR-PARSEFILE= \
+ '.if !target(__$${.PARSEDIR}/$${.PARSEFILE}__)' \
+ '__$${.PARSEDIR}/$${.PARSEFILE}__: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-indirect-PARSEDIR-PARSEFILE.tmp:1
+# expect: Skipping 'target-indirect-PARSEDIR-PARSEFILE.tmp' because '__target-indirect-PARSEDIR-PARSEFILE.tmp__' is defined
+# The actual target starts with '__${.OBJDIR}/', see the .rawout file, but the
+# string '${.OBJDIR}/' gets stripped in post processing.
+
+# Using the combination of '.PARSEDIR' and '.PARSEFILE', a file in a
+# subdirectory gets a different guard target name than the previous one.
+CASES+= subdir/target-indirect-PARSEDIR-PARSEFILE
+LINES.subdir/target-indirect-PARSEDIR-PARSEFILE= \
+ '.if !target(__$${.PARSEDIR}/$${.PARSEFILE}__)' \
+ '__$${.PARSEDIR}/$${.PARSEFILE}__: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: subdir/target-indirect-PARSEDIR-PARSEFILE.tmp:1
+# expect: Skipping 'subdir/target-indirect-PARSEDIR-PARSEFILE.tmp' because '__subdir/target-indirect-PARSEDIR-PARSEFILE.tmp__' is defined
+# The actual target starts with '__${.OBJDIR}/', see the .rawout file, but the
+# string '${.OBJDIR}/' gets stripped in post processing.
+
+# If the guard target is not defined when including the file the next time,
+# the file is processed again.
+CASES+= target-unguarded
+LINES.target-unguarded= \
+ '.if !target(target-unguarded)' \
+ '.endif'
+# expect: Parse_PushInput: target-unguarded.tmp:1
+# expect: Parse_PushInput: target-unguarded.tmp:1
+
+# The guard condition must consist of only the guard target, nothing else.
+CASES+= target-plus
+LINES.target-plus= \
+ '.if !target(target-plus) && 1' \
+ 'target-plus: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-plus.tmp:1
+# expect: Parse_PushInput: target-plus.tmp:1
+
+# If the guard target is defined before the file is included the first time,
+# the file is read once and then considered guarded.
+CASES+= target-already-defined
+LINES.target-already-defined= \
+ '.if !target(target-already-defined)' \
+ 'target-already-defined: .NOTMAIN' \
+ '.endif'
+target-already-defined: .NOTMAIN
+# expect: Parse_PushInput: target-already-defined.tmp:1
+# expect: Skipping 'target-already-defined.tmp' because 'target-already-defined' is defined
+
+# A target name cannot contain the character '!'. In the condition, the '!'
+# is syntactically valid, but in the dependency declaration line, the '!' is
+# interpreted as the '!' dependency operator, no matter whether it occurs at
+# the beginning or in the middle of a target name. Escaping it as '${:U!}'
+# doesn't work, as the whole line is first expanded and then scanned for the
+# dependency operator. Escaping it as '\!' doesn't work either, even though
+# the '\' escapes the '!' from being a dependency operator, but when reading
+# the target name, the '\' is kept, resulting in the target name
+# '\!target-name-exclamation' instead of '!target-name-exclamation'.
+CASES+= target-name-exclamation
+LINES.target-name-exclamation= \
+ '.if !target(!target-name-exclamation)' \
+ '\!target-name-exclamation: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-name-exclamation.tmp:1
+# expect: Parse_PushInput: target-name-exclamation.tmp:1
+
+# If the guard target name has leading spaces, it does not have an effect,
+# as that form is not common in practice.
+CASES+= target-name-leading-space
+LINES.target-name-leading-space= \
+ '.if !target( target-name-leading-space)' \
+ 'target-name-leading-space: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-name-leading-space.tmp:1
+# expect: Parse_PushInput: target-name-leading-space.tmp:1
+
+# If the guard target name has trailing spaces, it does not have an effect,
+# as that form is not common in practice.
+CASES+= target-name-trailing-space
+LINES.target-name-trailing-space= \
+ '.if !target(target-name-trailing-space )' \
+ 'target-name-trailing-space: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-name-trailing-space.tmp:1
+# expect: Parse_PushInput: target-name-trailing-space.tmp:1
+
+# If the guard target condition is enclosed in parentheses, it does not have
+# an effect, as that form is not common in practice.
+CASES+= target-call-parenthesized
+LINES.target-call-parenthesized= \
+ '.if (!target(target-call-parenthesized))' \
+ 'target-call-parenthesized: .NOTMAIN' \
+ '.endif'
+# expect: Parse_PushInput: target-call-parenthesized.tmp:1
+# expect: Parse_PushInput: target-call-parenthesized.tmp:1
+
+# If the '.if' or '.ifndef' directive spans more than a single line, it is
+# still recognized as a guard condition. This case is entirely uncommon, but
+# at the point where the guard condition is checked, line continuations have
+# already been converted to spaces.
+CASES+= multiline
+LINES.multiline= \
+ '.\' \
+ ' ifndef \' \
+ ' MULTILINE' \
+ 'MULTILINE=' \
+ '.endif'
+# expect: Parse_PushInput: multiline.tmp:1
+# expect: Skipping 'multiline.tmp' because 'MULTILINE' is defined
+
+
+# Now run all test cases by including each of the files twice and looking at
+# the debug output. The files that properly guard against multiple inclusion
+# generate a 'Skipping' line, the others repeat the 'Parse_PushInput' line.
+#
+# Some debug output lines are suppressed in the .exp file, see ./Makefile.
+.for i in ${CASES}
+. for fname in $i.tmp
+_:= ${fname:H:N.:@dir@${:!mkdir -p ${dir}!}@}
+_!= printf '%s\n' ${LINES.$i} > ${fname}
+.MAKEFLAGS: -dp
+.include "${.CURDIR}/${fname}"
+.undef ${UNDEF_BETWEEN.$i:U}
+.include "${.CURDIR}/${fname}"
+.MAKEFLAGS: -d0
+_!= rm ${fname}
+_:= ${fname:H:N.:@dir@${:!rmdir ${dir}!}@}
+. endfor
+.endfor
+
+all:
diff --git a/contrib/bmake/unit-tests/directive-include.exp b/contrib/bmake/unit-tests/directive-include.exp
index 0ddf40a75d2d..361389cf1cad 100755
--- a/contrib/bmake/unit-tests/directive-include.exp
+++ b/contrib/bmake/unit-tests/directive-include.exp
@@ -2,12 +2,13 @@ CondParser_Eval: ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null"
Comparing "directive-include.mk null" != "directive-include.mk null"
CondParser_Eval: ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null"
Comparing "directive-include.mk null" != "directive-include.mk null"
-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: directive-include.mk:26: Could not find nonexistent.mk
+make: directive-include.mk:49: Could not find "
+make: directive-include.mk:56: Unknown modifier ":Z"
+ while evaluating "${:U123:Z}.mk" with value "123"
+make: directive-include.mk:56: Could not find nonexistent.mk
+make: directive-include.mk:61: Cannot open /nonexistent
+make: directive-include.mk:66: Invalid line "include"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" 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 edf27d02483e..ad6936ab2f3c 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.11 2022/01/15 12:35:18 rillig Exp $
+# $NetBSD: directive-include.mk,v 1.20 2025/06/28 22:39:28 rillig Exp $
#
# Tests for the .include directive, which includes another file.
@@ -22,6 +22,7 @@
. error
.endif
+# expect+1: Could not find nonexistent.mk
.include "nonexistent.mk"
.include "/dev/null" # size 0
# including a directory technically succeeds, but shouldn't.
@@ -44,11 +45,14 @@ DEV= null
# would be empty, and the closing '"' would be in the trailing part of the
# line, which is ignored as of 2021-12-03.
DQUOT= "
+# expect+1: Could not find "
.include "${DQUOT}"
# When the expression in a filename cannot be evaluated, the failing
# expression is skipped and the file is included nevertheless.
# FIXME: Add proper error handling, no file must be included here.
+# expect+2: Unknown modifier ":Z"
+# expect+1: Could not find nonexistent.mk
.include "nonexistent${:U123:Z}.mk"
# The traditional include directive is seldom used.
@@ -58,7 +62,7 @@ include /nonexistent # comment
sinclude /nonexistent # comment
include ${:U/dev/null} # comment
include /dev/null /dev/null
-# expect+1: Invalid line type
+# expect+1: Invalid line "include"
include
# XXX: trailing whitespace in diagnostic, missing quotes around filename
@@ -66,7 +70,7 @@ include
# 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".
+# resulting in the error message ":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}
diff --git a/contrib/bmake/unit-tests/directive-info.exp b/contrib/bmake/unit-tests/directive-info.exp
index 70def02441d1..730174d6ab4a 100644
--- a/contrib/bmake/unit-tests/directive-info.exp
+++ b/contrib/bmake/unit-tests/directive-info.exp
@@ -1,15 +1,15 @@
-make: "directive-info.mk" line 11: begin .info tests
-make: "directive-info.mk" line 12: Unknown directive "inf"
-make: "directive-info.mk" line 13: Missing argument for ".info"
-make: "directive-info.mk" line 14: message
-make: "directive-info.mk" line 15: indented message
-make: "directive-info.mk" line 16: Unknown directive "information"
-make: "directive-info.mk" line 17: Unknown directive "information"
-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 35: expect line 35 for multi-line message
+make: directive-info.mk:12: begin .info tests
+make: directive-info.mk:14: Unknown directive "inf"
+make: directive-info.mk:16: Missing argument for ".info"
+make: directive-info.mk:18: message
+make: directive-info.mk:20: indented message
+make: directive-info.mk:22: Unknown directive "information"
+make: directive-info.mk:24: Unknown directive "information"
+make: directive-info.mk:30: Missing argument for ".info"
+make: directive-info.mk:32: Missing argument for ".info"
+make: directive-info.mk:36: Unknown directive "info-message"
+make: directive-info.mk:38: no-target: no-source
+make: directive-info.mk:47: expect line 35 for multi-line message
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making ".info.man" 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 54f6a0f5aad0..ab550555d44f 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.9 2022/01/08 20:21:34 rillig Exp $
+# $NetBSD: directive-info.mk,v 1.11 2023/06/01 20:56:35 rillig Exp $
#
# Tests for the .info directive.
#
@@ -8,22 +8,33 @@
# TODO: Implementation
+# expect+1: begin .info tests
.info begin .info tests
+# expect+1: Unknown directive "inf"
.inf # misspelled
-.info # "Missing argument"
+# expect+1: Missing argument for ".info"
+.info
+# expect+1: message
.info message
+# expect+1: indented message
.info indented message
+# expect+1: Unknown directive "information"
.information
+# expect+1: Unknown directive "information"
.information message # Accepted before 2020-12-13 01:07:54.
.info.man: # not a message, but possibly a suffix rule
# Even if lines would have trailing whitespace, this would be trimmed by
-# ParseGetLine.
+# ParseRawLine.
+# expect+1: Missing argument for ".info"
.info
+# expect+1: Missing argument for ".info"
.info # comment
.info: message # This is a dependency declaration.
+# expect+1: Unknown directive "info-message"
.info-message # This is an unknown directive.
+# expect+1: no-target: no-source
.info no-target: no-source # This is a .info directive, not a dependency.
# See directive.mk for more tests of this kind.
@@ -32,9 +43,7 @@
# 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.
+# expect+1: expect line 35 for multi-line message
.info expect line 35 for\
multi$\
-line message
-
-all:
- @:;
diff --git a/contrib/bmake/unit-tests/directive-misspellings.exp b/contrib/bmake/unit-tests/directive-misspellings.exp
index e51d8473b305..c551d73d5f98 100644
--- a/contrib/bmake/unit-tests/directive-misspellings.exp
+++ b/contrib/bmake/unit-tests/directive-misspellings.exp
@@ -1,45 +1,45 @@
-make: "directive-misspellings.mk" line 12: Unknown directive "dinclud"
-make: "directive-misspellings.mk" line 14: Unknown directive "dincludx"
-make: "directive-misspellings.mk" line 15: .include filename must be delimited by '"' or '<'
-make: "directive-misspellings.mk" line 17: Unknown directive "erro"
-make: "directive-misspellings.mk" line 18: Unknown directive "errox"
-make: "directive-misspellings.mk" line 22: Unknown directive "expor"
-make: "directive-misspellings.mk" line 24: Unknown directive "exporx"
-make: "directive-misspellings.mk" line 25: Unknown directive "exports"
-make: "directive-misspellings.mk" line 27: Unknown directive "export-en"
-make: "directive-misspellings.mk" line 30: Unknown directive "export-environment"
-make: "directive-misspellings.mk" line 32: Unknown directive "export-litera"
-make: "directive-misspellings.mk" line 34: Unknown directive "export-literax"
-make: "directive-misspellings.mk" line 35: Unknown directive "export-literally"
-make: "directive-misspellings.mk" line 37: Unknown directive "-includ"
-make: "directive-misspellings.mk" line 39: Unknown directive "-includx"
-make: "directive-misspellings.mk" line 40: .include filename must be delimited by '"' or '<'
-make: "directive-misspellings.mk" line 42: Unknown directive "includ"
-make: "directive-misspellings.mk" line 43: Could not find file
-make: "directive-misspellings.mk" line 44: Unknown directive "includx"
-make: "directive-misspellings.mk" line 45: .include filename must be delimited by '"' or '<'
-make: "directive-misspellings.mk" line 47: Unknown directive "inf"
-make: "directive-misspellings.mk" line 48: msg
-make: "directive-misspellings.mk" line 49: Unknown directive "infx"
-make: "directive-misspellings.mk" line 50: Unknown directive "infos"
-make: "directive-misspellings.mk" line 52: Unknown directive "sinclud"
-make: "directive-misspellings.mk" line 54: Unknown directive "sincludx"
-make: "directive-misspellings.mk" line 55: .include filename must be delimited by '"' or '<'
-make: "directive-misspellings.mk" line 57: Unknown directive "unde"
-make: "directive-misspellings.mk" line 59: Unknown directive "undex"
-make: "directive-misspellings.mk" line 60: Unknown directive "undefs"
-make: "directive-misspellings.mk" line 62: Unknown directive "unexpor"
-make: "directive-misspellings.mk" line 64: Unknown directive "unexporx"
-make: "directive-misspellings.mk" line 65: Unknown directive "unexports"
-make: "directive-misspellings.mk" line 67: Unknown directive "unexport-en"
-make: "directive-misspellings.mk" line 69: The directive .unexport-env does not take arguments
-make: "directive-misspellings.mk" line 70: Unknown directive "unexport-enx"
-make: "directive-misspellings.mk" line 71: Unknown directive "unexport-envs"
-make: "directive-misspellings.mk" line 73: Unknown directive "warn"
-make: "directive-misspellings.mk" line 74: Unknown directive "warnin"
-make: "directive-misspellings.mk" line 75: warning: msg
-make: "directive-misspellings.mk" line 76: Unknown directive "warninx"
-make: "directive-misspellings.mk" line 77: Unknown directive "warnings"
+make: directive-misspellings.mk:13: Unknown directive "dinclud"
+make: directive-misspellings.mk:16: Unknown directive "dincludx"
+make: directive-misspellings.mk:18: .include filename must be delimited by "" or <>
+make: directive-misspellings.mk:21: Unknown directive "erro"
+make: directive-misspellings.mk:23: Unknown directive "errox"
+make: directive-misspellings.mk:28: Unknown directive "expor"
+make: directive-misspellings.mk:31: Unknown directive "exporx"
+make: directive-misspellings.mk:33: Unknown directive "exports"
+make: directive-misspellings.mk:36: Unknown directive "export-en"
+make: directive-misspellings.mk:40: Unknown directive "export-environment"
+make: directive-misspellings.mk:43: Unknown directive "export-litera"
+make: directive-misspellings.mk:46: Unknown directive "export-literax"
+make: directive-misspellings.mk:48: Unknown directive "export-literally"
+make: directive-misspellings.mk:51: Unknown directive "-includ"
+make: directive-misspellings.mk:54: Unknown directive "-includx"
+make: directive-misspellings.mk:56: .include filename must be delimited by "" or <>
+make: directive-misspellings.mk:59: Unknown directive "includ"
+make: directive-misspellings.mk:61: Could not find file
+make: directive-misspellings.mk:63: Unknown directive "includx"
+make: directive-misspellings.mk:65: .include filename must be delimited by "" or <>
+make: directive-misspellings.mk:68: Unknown directive "inf"
+make: directive-misspellings.mk:70: msg
+make: directive-misspellings.mk:72: Unknown directive "infx"
+make: directive-misspellings.mk:74: Unknown directive "infos"
+make: directive-misspellings.mk:77: Unknown directive "sinclud"
+make: directive-misspellings.mk:80: Unknown directive "sincludx"
+make: directive-misspellings.mk:82: .include filename must be delimited by "" or <>
+make: directive-misspellings.mk:85: Unknown directive "unde"
+make: directive-misspellings.mk:88: Unknown directive "undex"
+make: directive-misspellings.mk:90: Unknown directive "undefs"
+make: directive-misspellings.mk:93: Unknown directive "unexpor"
+make: directive-misspellings.mk:96: Unknown directive "unexporx"
+make: directive-misspellings.mk:98: Unknown directive "unexports"
+make: directive-misspellings.mk:101: Unknown directive "unexport-en"
+make: directive-misspellings.mk:104: The directive .unexport-env does not take arguments
+make: directive-misspellings.mk:106: Unknown directive "unexport-enx"
+make: directive-misspellings.mk:108: Unknown directive "unexport-envs"
+make: directive-misspellings.mk:111: Unknown directive "warn"
+make: directive-misspellings.mk:113: Unknown directive "warnin"
+make: directive-misspellings.mk:115: warning: msg
+make: directive-misspellings.mk:117: Unknown directive "warninx"
+make: directive-misspellings.mk:119: 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-misspellings.mk b/contrib/bmake/unit-tests/directive-misspellings.mk
index 5f479f03b7f1..cd6222b378f5 100644
--- a/contrib/bmake/unit-tests/directive-misspellings.mk
+++ b/contrib/bmake/unit-tests/directive-misspellings.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-misspellings.mk,v 1.3 2020/12/13 01:10:22 rillig Exp $
+# $NetBSD: directive-misspellings.mk,v 1.5 2025/06/28 22:39:28 rillig Exp $
#
# Tests for misspelled directives.
#
@@ -9,71 +9,111 @@
# ".information" were aliases to ".info" since the code for these diagnostic
# directives just skipped any letters following the "error", "warn" or "info".
+# expect+1: Unknown directive "dinclud"
.dinclud "file"
.dinclude "file"
+# expect+1: Unknown directive "dincludx"
.dincludx "file"
+# expect+1: .include filename must be delimited by "" or <>
.dincludes "file" # XXX: the 's' is not meant to be a filename
+# expect+1: Unknown directive "erro"
.erro msg
+# expect+1: Unknown directive "errox"
.errox msg
# no .error since that would exit immediately
# no .errors since that would exit immediately, even with the typo
+# expect+1: Unknown directive "expor"
.expor varname
.export varname
+# expect+1: Unknown directive "exporx"
.exporx varname
+# expect+1: Unknown directive "exports"
.exports varname # Accepted before 2020-12-13 01:07:54.
+# expect+1: Unknown directive "export-en"
.export-en # Accepted before 2020-12-13 01:07:54.
.export-env
.export-env extra argument # XXX: undetected extra argument
+# expect+1: Unknown directive "export-environment"
.export-environment # Accepted before 2020-12-13 01:07:54.
+# expect+1: Unknown directive "export-litera"
.export-litera varname # Accepted before 2020-12-13 01:07:54.
.export-literal varname
+# expect+1: Unknown directive "export-literax"
.export-literax varname # Accepted before 2020-12-13 01:07:54.
+# expect+1: Unknown directive "export-literally"
.export-literally varname # Accepted before 2020-12-13 01:07:54.
+# expect+1: Unknown directive "-includ"
.-includ "file"
.-include "file"
+# expect+1: Unknown directive "-includx"
.-includx "file"
+# expect+1: .include filename must be delimited by "" or <>
.-includes "file" # XXX: the 's' is not meant to be a filename
+# expect+1: Unknown directive "includ"
.includ "file"
+# expect+1: Could not find file
.include "file"
+# expect+1: Unknown directive "includx"
.includx "file"
+# expect+1: .include filename must be delimited by "" or <>
.includex "file" # XXX: the 's' is not meant to be a filename
+# expect+1: Unknown directive "inf"
.inf msg
+# expect+1: msg
.info msg
+# expect+1: Unknown directive "infx"
.infx msg
+# expect+1: Unknown directive "infos"
.infos msg # Accepted before 2020-12-13 01:07:54.
+# expect+1: Unknown directive "sinclud"
.sinclud "file"
.sinclude "file"
+# expect+1: Unknown directive "sincludx"
.sincludx "file"
+# expect+1: .include filename must be delimited by "" or <>
.sincludes "file" # XXX: the 's' is not meant to be a filename
+# expect+1: Unknown directive "unde"
.unde varname
.undef varname
+# expect+1: Unknown directive "undex"
.undex varname
+# expect+1: Unknown directive "undefs"
.undefs varname # Accepted before 2020-12-13 01:07:54.
+# expect+1: Unknown directive "unexpor"
.unexpor varname
.unexport varname
+# expect+1: Unknown directive "unexporx"
.unexporx varname
+# expect+1: Unknown directive "unexports"
.unexports varname # Accepted before 2020-12-12 18:00:18.
+# expect+1: Unknown directive "unexport-en"
.unexport-en # Accepted before 2020-12-12 18:11:42.
.unexport-env
+# expect+1: The directive .unexport-env does not take arguments
.unexport-env extra argument # Accepted before 2020-12-12 18:00:18.
+# expect+1: Unknown directive "unexport-enx"
.unexport-enx # Accepted before 2020-12-12 18:00:18.
+# expect+1: Unknown directive "unexport-envs"
.unexport-envs # Accepted before 2020-12-12 18:00:18.
+# expect+1: Unknown directive "warn"
.warn msg
+# expect+1: Unknown directive "warnin"
.warnin msg
+# expect+1: warning: msg
.warning msg
+# expect+1: Unknown directive "warninx"
.warninx msg
+# expect+1: Unknown directive "warnings"
.warnings msg # Accepted before 2020-12-13 01:07:54.
-
-all:
diff --git a/contrib/bmake/unit-tests/directive-sinclude.exp b/contrib/bmake/unit-tests/directive-sinclude.exp
index ffdfefca0d4f..74db51227f07 100755
--- a/contrib/bmake/unit-tests/directive-sinclude.exp
+++ b/contrib/bmake/unit-tests/directive-sinclude.exp
@@ -1,4 +1,5 @@
-make: "directive-include-error.inc" line 1: Invalid line type
+make: directive-include-error.inc:1: Invalid line "syntax error"
+ in directive-sinclude.mk:20
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" 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 a935ea2c068f..4c856d22be4f 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.4 2022/01/23 21:48:59 rillig Exp $
+# $NetBSD: directive-sinclude.mk,v 1.7 2025/06/28 22:39:28 rillig Exp $
#
# Tests for the .sinclude directive, which includes another file,
# silently skipping it if it cannot be opened.
@@ -15,7 +15,7 @@
.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
+# expect: make: directive-include-error.inc:1: Invalid line "syntax error"
_!= echo 'syntax error' > directive-include-error.inc
.sinclude "${.CURDIR}/directive-include-error.inc"
_!= rm directive-include-error.inc
diff --git a/contrib/bmake/unit-tests/directive-undef.exp b/contrib/bmake/unit-tests/directive-undef.exp
index 20df58a8dc73..f67be532c74f 100644
--- a/contrib/bmake/unit-tests/directive-undef.exp
+++ b/contrib/bmake/unit-tests/directive-undef.exp
@@ -1,6 +1,7 @@
-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 102: warning: UT_EXPORTED is still listed in .MAKE.EXPORTED even though spaceit is not exported anymore.
+make: directive-undef.mk:30: The .undef directive requires an argument
+make: directive-undef.mk:88: Unknown modifier ":Z"
+ while evaluating variable "VARNAMES" with value "VARNAMES"
+make: directive-undef.mk:105: 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
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-undef.mk b/contrib/bmake/unit-tests/directive-undef.mk
index 5ac7d939c71e..51867ac6f754 100644
--- a/contrib/bmake/unit-tests/directive-undef.mk
+++ b/contrib/bmake/unit-tests/directive-undef.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-undef.mk,v 1.12 2022/03/26 12:44:57 rillig Exp $
+# $NetBSD: directive-undef.mk,v 1.17 2025/03/29 19:08:52 rillig Exp $
#
# Tests for the .undef directive.
#
@@ -26,6 +26,7 @@
# to delete the variable with the empty name, which never exists; see
# varname-empty.mk. Since var.c 1.737 from 2020-12-19, .undef complains
# about a missing argument.
+# expect+1: The .undef directive requires an argument
.undef
@@ -83,6 +84,7 @@ ${DOLLAR}= dollar
#
# As of var.c 1.762, this doesn't happen though because the error handling
# in Var_Parse and Var_Subst is not done properly.
+# expect+1: Unknown modifier ":Z"
.undef ${VARNAMES:L:Z}
@@ -99,6 +101,7 @@ UT_EXPORTED= exported-value
. error
.endif
.if ${.MAKE.EXPORTED:MUT_EXPORTED}
+# expect+1: warning: UT_EXPORTED is still listed in .MAKE.EXPORTED even though spaceit is not exported anymore.
. warning UT_EXPORTED is still listed in .MAKE.EXPORTED even though $\
it is not exported anymore.
.endif
diff --git a/contrib/bmake/unit-tests/directive-unexport-env.exp b/contrib/bmake/unit-tests/directive-unexport-env.exp
index 22528c31c3a1..cecebf3ef035 100644
--- a/contrib/bmake/unit-tests/directive-unexport-env.exp
+++ b/contrib/bmake/unit-tests/directive-unexport-env.exp
@@ -1,9 +1,9 @@
-make: "directive-unexport-env.mk" line 13: Unknown directive "unexport-en"
-make: "directive-unexport-env.mk" line 15: Unknown directive "unexport-environment"
+make: directive-unexport-env.mk:14: Unknown directive "unexport-en"
+make: directive-unexport-env.mk:17: Unknown directive "unexport-environment"
Global: UT_EXPORTED = value
Global: UT_UNEXPORTED = value
Global: .MAKE.EXPORTED = UT_EXPORTED
-make: "directive-unexport-env.mk" line 21: The directive .unexport-env does not take arguments
+make: directive-unexport-env.mk:24: The directive .unexport-env does not take arguments
Var_Parse: ${.MAKE.EXPORTED:O:u} (eval)
Evaluating modifier ${.MAKE.EXPORTED:O} on value "UT_EXPORTED"
Result of ${.MAKE.EXPORTED:O} is "UT_EXPORTED"
@@ -14,5 +14,5 @@ Global: delete .MAKE.EXPORTED
Global: .MAKEFLAGS = -r -k -d v -d
Global: .MAKEFLAGS = -r -k -d v -d 0
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" 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 e9620684dfcb..e56e47865011 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.8 2022/01/23 16:09:38 rillig Exp $
+# $NetBSD: directive-unexport-env.mk,v 1.9 2023/06/01 20:56:35 rillig Exp $
#
# Tests for the .unexport-env directive.
#
@@ -10,14 +10,17 @@
# TODO: Implementation
+# expect+1: Unknown directive "unexport-en"
.unexport-en # misspelled
.unexport-env # ok
+# expect+1: Unknown directive "unexport-environment"
.unexport-environment # misspelled
.MAKEFLAGS: -dv
UT_EXPORTED= value
UT_UNEXPORTED= value
.export UT_EXPORTED
+# expect+1: The directive .unexport-env does not take arguments
.unexport-env UT_EXPORTED UT_UNEXPORTED
.MAKEFLAGS: -d0
diff --git a/contrib/bmake/unit-tests/directive-unexport.exp b/contrib/bmake/unit-tests/directive-unexport.exp
index d59fb4713259..25bab7d7fd35 100644
--- a/contrib/bmake/unit-tests/directive-unexport.exp
+++ b/contrib/bmake/unit-tests/directive-unexport.exp
@@ -1,5 +1,5 @@
-make: "directive-unexport.mk" line 18: UT_A=a UT_B=b UT_C=c
-make: "directive-unexport.mk" line 19: UT_A UT_B UT_C
-make: "directive-unexport.mk" line 27: UT_A=a UT_B=b UT_C=c
-make: "directive-unexport.mk" line 28:
+make: directive-unexport.mk:19: UT_A=a UT_B=b UT_C=c
+make: directive-unexport.mk:21: UT_A UT_B UT_C
+make: directive-unexport.mk:30: UT_A=a UT_B=b UT_C=c
+make: directive-unexport.mk:32:
exit status 0
diff --git a/contrib/bmake/unit-tests/directive-unexport.mk b/contrib/bmake/unit-tests/directive-unexport.mk
index efc103efedf6..3c10ffa07d6a 100644
--- a/contrib/bmake/unit-tests/directive-unexport.mk
+++ b/contrib/bmake/unit-tests/directive-unexport.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-unexport.mk,v 1.7 2020/12/13 01:07:54 rillig Exp $
+# $NetBSD: directive-unexport.mk,v 1.9 2025/06/30 21:44:39 rillig Exp $
#
# Tests for the .unexport directive.
#
@@ -15,7 +15,9 @@ UT_C= c
.export UT_A UT_B UT_C
# Show the exported variables and their values.
+# expect+1: UT_A=a UT_B=b UT_C=c
.info ${:!env|sort|grep '^UT_'!}
+# expect+1: UT_A UT_B UT_C
.info ${.MAKE.EXPORTED}
# XXX: Now try to unexport all of them. The variables are still exported
@@ -24,7 +26,9 @@ UT_C= c
*= asterisk
.unexport *
+# expect+1: UT_A=a UT_B=b UT_C=c
.info ${:!env|sort|grep '^UT_'!}
+# expect+1:
.info ${.MAKE.EXPORTED}
.unexport # oops: missing argument
diff --git a/contrib/bmake/unit-tests/directive-warning.exp b/contrib/bmake/unit-tests/directive-warning.exp
index 932b88a151e2..250e32583847 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 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: directive-warning.mk:10: Unknown directive "warn"
+make: directive-warning.mk:12: Unknown directive "warn"
+make: directive-warning.mk:14: Unknown directive "warnin"
+make: directive-warning.mk:16: Unknown directive "warnin"
+make: directive-warning.mk:18: Missing argument for ".warning"
+make: directive-warning.mk:20: warning: message
+make: directive-warning.mk:22: Unknown directive "warnings"
+make: directive-warning.mk:24: Unknown directive "warnings"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" 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 9d5cec1ff0b8..50666487c13f 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.7 2022/01/23 16:09:38 rillig Exp $
+# $NetBSD: directive-warning.mk,v 1.10 2025/07/01 04:24:20 rillig Exp $
#
# Tests for the .warning directive.
#
@@ -6,13 +6,21 @@
# produced the wrong error message "Unknown directive". Since parse.c 1.503
# from 2020-12-19, the correct "Missing argument" is produced.
+# expect+1: Unknown directive "warn"
.warn # misspelled
+# expect+1: Unknown directive "warn"
.warn message # misspelled
+# expect+1: Unknown directive "warnin"
.warnin # misspelled
+# expect+1: Unknown directive "warnin"
.warnin message # misspelled
+# expect+1: Missing argument for ".warning"
.warning # "Missing argument"
-.warning message # expect+0: message
+# expect+1: warning: message
+.warning message
+# expect+1: Unknown directive "warnings"
.warnings # misspelled
+# expect+1: Unknown directive "warnings"
.warnings messages # Accepted before 2020-12-13 01:07:54.
all: .PHONY
diff --git a/contrib/bmake/unit-tests/directive.exp b/contrib/bmake/unit-tests/directive.exp
index d7d918fb24f3..dce759abfe52 100644
--- a/contrib/bmake/unit-tests/directive.exp
+++ b/contrib/bmake/unit-tests/directive.exp
@@ -1,14 +1,14 @@
-make: "directive.mk" line 10: Unknown directive "indented"
-make: "directive.mk" line 12: Unknown directive "indented"
-make: "directive.mk" line 14: Unknown directive "indented"
-make: "directive.mk" line 21: Unknown directive "info"
+make: directive.mk:10: Unknown directive "indented"
+make: directive.mk:12: Unknown directive "indented"
+make: directive.mk:14: Unknown directive "indented"
+make: directive.mk:19: Unknown directive ""
Global: .info = # (empty)
Global: .info = value
-make: "directive.mk" line 33: := value
+make: directive.mk:31: := 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: directive.mk:40: Invalid line "target-without-colon"
+make: directive.mk:43: Invalid line "target-without-colon another-target"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making ".target" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive.mk b/contrib/bmake/unit-tests/directive.mk
index 365a070f8f30..5f5be5aa0fab 100644
--- a/contrib/bmake/unit-tests/directive.mk
+++ b/contrib/bmake/unit-tests/directive.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive.mk,v 1.6 2022/01/23 16:09:38 rillig Exp $
+# $NetBSD: directive.mk,v 1.10 2025/06/28 22:39:28 rillig Exp $
#
# Tests for the preprocessing directives, such as .if or .info.
@@ -13,11 +13,9 @@
# expect+1: Unknown directive "indented"
. indented tab
-# Directives must be written directly, not indirectly via variable
+# Directives must be written directly, not indirectly via
# 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"
+# expect+1: Unknown directive ""
.${:Uinfo} directives cannot be indirect
# There is no directive called '.target', therefore this is parsed as a
@@ -38,8 +36,8 @@
# Not even the space after the '.info' can change anything about this.
.${:Uinfo} : source
-# expect+1: Invalid line type
+# expect+1: Invalid line "target-without-colon"
target-without-colon
-# expect+1: Invalid line type
+# expect+1: Invalid line "target-without-colon another-target"
target-without-colon another-target
diff --git a/contrib/bmake/unit-tests/doterror.exp b/contrib/bmake/unit-tests/doterror.exp
index 5655644c32e2..1d7e41961c48 100644
--- a/contrib/bmake/unit-tests/doterror.exp
+++ b/contrib/bmake/unit-tests/doterror.exp
@@ -4,6 +4,6 @@ and now: sad
*** Error code 1
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
.ERROR: Looks like 'sad' is upset.
exit status 1
diff --git a/contrib/bmake/unit-tests/doterror.mk b/contrib/bmake/unit-tests/doterror.mk
index d46fb3581a25..0f3698ad5cf2 100644
--- a/contrib/bmake/unit-tests/doterror.mk
+++ b/contrib/bmake/unit-tests/doterror.mk
@@ -1,4 +1,4 @@
-# $NetBSD: doterror.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $
+# $NetBSD: doterror.mk,v 1.3 2023/06/01 20:56:35 rillig Exp $
.BEGIN:
@@ -17,4 +17,3 @@ happy:
sad:
@echo and now: $@; exit 1
-
diff --git a/contrib/bmake/unit-tests/error.exp b/contrib/bmake/unit-tests/error.exp
index 3adc099a4625..3742be10b1a8 100644
--- a/contrib/bmake/unit-tests/error.exp
+++ b/contrib/bmake/unit-tests/error.exp
@@ -1,6 +1,6 @@
-make: "error.mk" line 6: just FYI
-make: "error.mk" line 7: warning: this could be serious
-make: "error.mk" line 8: this is fatal
+make: error.mk:7: just FYI
+make: error.mk:9: warning: this could be serious
+make: error.mk:11: this is fatal
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/error.mk b/contrib/bmake/unit-tests/error.mk
index 0029b3bc6aa9..2383e60da6fb 100644
--- a/contrib/bmake/unit-tests/error.mk
+++ b/contrib/bmake/unit-tests/error.mk
@@ -1,10 +1,13 @@
-# $NetBSD: error.mk,v 1.3 2020/11/03 17:38:45 rillig Exp $
+# $NetBSD: error.mk,v 1.4 2023/06/01 20:56:35 rillig Exp $
#
# Demonstrate that the .error directive exits immediately, without
# continuing parsing until the end of the file.
+# expect+1: just FYI
.info just FYI
+# expect+1: warning: this could be serious
.warning this could be serious
+# expect+1: this is fatal
.error this is fatal
.info this is not reached because of the .error above
diff --git a/contrib/bmake/unit-tests/escape.exp b/contrib/bmake/unit-tests/escape.exp
index 6238e27b0191..ff9c8b7cf811 100644
--- a/contrib/bmake/unit-tests/escape.exp
+++ b/contrib/bmake/unit-tests/escape.exp
@@ -1,5 +1,4 @@
var-1bs
-printf "%s=:%s:\n" VAR1BS 111\\111; printf "%s=:%s:\n" VAR1BSa 111\\aaa; printf "%s=:%s:\n" VAR1BSA 111\\aaa; printf "%s=:%s:\n" VAR1BSda 111\\\$\{a\}; printf "%s=:%s:\n" VAR1BSdA 111\\\$\{A\}; printf "%s=:%s:\n" VAR1BSc 111\#\ backslash\ escapes\ comment\ char,\ so\ this\ is\ part\ of\ the\ value; printf "%s=:%s:\n" VAR1BSsc 111\\\ ;
VAR1BS=:111\111:
VAR1BSa=:111\aaa:
VAR1BSA=:111\aaa:
@@ -8,25 +7,22 @@ VAR1BSdA=:111\${A}:
VAR1BSc=:111# backslash escapes comment char, so this is part of the value:
VAR1BSsc=:111\ :
var-2bs
-printf "%s=:%s:\n" VAR2BS 222\\\\222; printf "%s=:%s:\n" VAR2BSa 222\\\\aaa; printf "%s=:%s:\n" VAR2BSA 222\\\\aaa; printf "%s=:%s:\n" VAR2BSda 222\\\\\$\{a\}; printf "%s=:%s:\n" VAR2BSdA 222\\\\\$\{A\}; printf "%s=:%s:\n" VAR2BSc 222\\\\; printf "%s=:%s:\n" VAR2BSsc 222\\\\;
-VAR2BS=:222\\222:
-VAR2BSa=:222\\aaa:
-VAR2BSA=:222\\aaa:
-VAR2BSda=:222\\${a}:
-VAR2BSdA=:222\\${A}:
-VAR2BSc=:222\\:
-VAR2BSsc=:222\\:
-var-1bsnl
-printf "%s=:%s:\n" VAR1BSNL 111\ 111; printf "%s=:%s:\n" VAR1BSNLa 111\ aaa; printf "%s=:%s:\n" VAR1BSNLA 111\ aaa; printf "%s=:%s:\n" VAR1BSNLda 111\ \$\{a\}; printf "%s=:%s:\n" VAR1BSNLdA 111\ \$\{A\}; printf "%s=:%s:\n" VAR1BSNLc 111; printf "%s=:%s:\n" VAR1BSNLsc 111;
-VAR1BSNL=:111 111:
-VAR1BSNLa=:111 aaa:
-VAR1BSNLA=:111 aaa:
-VAR1BSNLda=:111 ${a}:
-VAR1BSNLdA=:111 ${A}:
-VAR1BSNLc=:111:
-VAR1BSNLsc=:111:
+VAR2.BS=:222\\222:
+VAR2.BS.a=:222\\aaa:
+VAR2.BS.A=:222\\aaa:
+VAR2.BS.d.a=:222\\${a}:
+VAR2.BS.d.A=:222\\${A}:
+VAR2.BS.c=:222\\:
+VAR2.BS.s.c=:222\\:
+var-1bs-nl
+VAR1.BS-NL=:111 111:
+VAR1.BS-NL.a=:111 aaa:
+VAR1.BS-NL.A=:111 aaa:
+VAR1.BS-NL.d-a=:111 ${a}:
+VAR1.BS-NL.d-A=:111 ${A}:
+VAR1.BS-NL.c=:111:
+VAR1.BS-NL.s-c=:111:
var-2bsnl
-printf "%s=:%s:\n" VAR2BSNL 222\\\\; printf "%s=:%s:\n" VAR2BSNLa 222\\\\; printf "%s=:%s:\n" VAR2BSNLA 222\\\\; printf "%s=:%s:\n" VAR2BSNLda 222\\\\; printf "%s=:%s:\n" VAR2BSNLdA 222\\\\; printf "%s=:%s:\n" VAR2BSNLc 222\\\\; printf "%s=:%s:\n" VAR2BSNLsc 222\\\\;
VAR2BSNL=:222\\:
VAR2BSNLa=:222\\:
VAR2BSNLA=:222\\:
@@ -35,7 +31,6 @@ VAR2BSNLdA=:222\\:
VAR2BSNLc=:222\\:
VAR2BSNLsc=:222\\:
var-3bsnl
-printf "%s=:%s:\n" VAR3BSNL 333\\\\\ 333=; printf "%s=:%s:\n" VAR3BSNLa 333\\\\\ aaa=; printf "%s=:%s:\n" VAR3BSNLA 333\\\\\ aaa=; printf "%s=:%s:\n" VAR3BSNLda 333\\\\\ \$\{a\}=; printf "%s=:%s:\n" VAR3BSNLdA 333\\\\\ \$\{A\}=; printf "%s=:%s:\n" VAR3BSNLc 333\\\\; printf "%s=:%s:\n" VAR3BSNLsc 333\\\\;
VAR3BSNL=:333\\ 333=:
VAR3BSNLa=:333\\ aaa=:
VAR3BSNLA=:333\\ aaa=:
@@ -44,7 +39,6 @@ VAR3BSNLdA=:333\\ ${A}=:
VAR3BSNLc=:333\\:
VAR3BSNLsc=:333\\:
var-1bsnl-space
-printf "%s=:%s:\n" VAR1BSNL00 first\ line; printf "%s=:%s:\n" VAR1BSNL0 first\ line\ no\ space\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLs first\ line\ one\ space\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLss first\ line\ two\ spaces\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLt first\ line\ one\ tab\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLtt first\ line\ two\ tabs\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLxx first\ line\ many\ spaces\ and\ tabs\ \[\ \ \ \ \]\ on\ second\ line;
VAR1BSNL00=:first line:
VAR1BSNL0=:first line no space on second line:
VAR1BSNLs=:first line one space on second line:
diff --git a/contrib/bmake/unit-tests/escape.mk b/contrib/bmake/unit-tests/escape.mk
index 8bdd3ad2ab49..a363a19f1946 100644
--- a/contrib/bmake/unit-tests/escape.mk
+++ b/contrib/bmake/unit-tests/escape.mk
@@ -1,4 +1,4 @@
-# $NetBSD: escape.mk,v 1.14 2020/11/03 17:38:45 rillig Exp $
+# $NetBSD: escape.mk,v 1.15 2023/10/19 18:24:33 rillig Exp $
#
# Test backslash escaping.
@@ -53,7 +53,7 @@ should continue the comment. \
__printvars: .USE .MADE
@echo ${.TARGET}
- ${.ALLSRC:@v@ printf "%s=:%s:\n" ${v:Q} ${${v}:Q}; @}
+ @${.ALLSRC:@v@ printf "%s=:%s:\n" ${v:Q} ${${v}:Q}; @}
# Embedded backslash in variable should be taken literally.
#
@@ -83,7 +83,8 @@ all: var-2bs
var-2bs: .PHONY __printvars VAR2BS VAR2BSa VAR2BSA VAR2BSda VAR2BSdA \
VAR2BSc VAR2BSsc
-# Backslash-newline in a variable setting is replaced by a single space.
+# In a variable assignment, when the sequence <backslash><newline> occurs at
+# the end of a physical line, it is replaced with a single space.
#
VAR1BSNL= 111\
111
diff --git a/contrib/bmake/unit-tests/export-all.mk b/contrib/bmake/unit-tests/export-all.mk
index 0d741083441b..bf1ecd5716e9 100644
--- a/contrib/bmake/unit-tests/export-all.mk
+++ b/contrib/bmake/unit-tests/export-all.mk
@@ -1,4 +1,4 @@
-# $NetBSD: export-all.mk,v 1.5 2020/10/24 08:50:17 rillig Exp $
+# $NetBSD: export-all.mk,v 1.6 2024/06/01 06:26:36 sjg Exp $
UT_OK= good
UT_F= fine
@@ -15,7 +15,7 @@ UT_BADDIR= ${${here}/../${here:T}:L:${M_tAbad}:T}
# this will be ok
UT_OKDIR= ${${here}/../${here:T}:L:${M_tA}:T}
-.export
+.export-all
FILTER_CMD= grep ^UT_
.include "export.mk"
diff --git a/contrib/bmake/unit-tests/export-env.mk b/contrib/bmake/unit-tests/export-env.mk
index 1605b1a71d61..80653f4bb3c9 100644
--- a/contrib/bmake/unit-tests/export-env.mk
+++ b/contrib/bmake/unit-tests/export-env.mk
@@ -1,4 +1,4 @@
-# $NetBSD: export-env.mk,v 1.4 2020/10/24 08:50:17 rillig Exp $
+# $NetBSD: export-env.mk,v 1.5 2023/06/01 20:56:35 rillig Exp $
# our normal .export, subsequent changes affect the environment
UT_TEST= this
@@ -21,7 +21,3 @@ UT_LIT= literal ${UT_TEST}
all:
@echo make:; ${UT_TEST UT_ENV UT_EXP UT_LIT:L:@v@echo $v=${$v};@}
@echo env:; ${UT_TEST UT_ENV UT_EXP UT_LIT:L:@v@echo $v=$${$v};@}
-
-
-
-
diff --git a/contrib/bmake/unit-tests/export.mk b/contrib/bmake/unit-tests/export.mk
index bab08ee3ea23..38670eaaaf48 100644
--- a/contrib/bmake/unit-tests/export.mk
+++ b/contrib/bmake/unit-tests/export.mk
@@ -1,4 +1,4 @@
-# $NetBSD: export.mk,v 1.11 2021/12/05 14:57:36 rillig Exp $
+# $NetBSD: export.mk,v 1.12 2022/09/09 18:36:15 sjg Exp $
UT_TEST= export
UT_FOO= foo${BAR}
@@ -40,7 +40,7 @@ BAR= bar is ${UT_FU}
.MAKE.EXPORTED+= UT_ZOO UT_TEST
-FILTER_CMD?= egrep -v '^(MAKEFLAGS|MALLOC_.*|PATH|PWD|SHLVL|_|&)='
+FILTER_CMD?= ${EGREP} -v '^(MAKEFLAGS|MALLOC_.*|PATH|PWD|SHLVL|_|&)='
all:
@env | ${FILTER_CMD} | sort
diff --git a/contrib/bmake/unit-tests/forloop.exp b/contrib/bmake/unit-tests/forloop.exp
deleted file mode 100644
index 422711b41247..000000000000
--- a/contrib/bmake/unit-tests/forloop.exp
+++ /dev/null
@@ -1,20 +0,0 @@
-make: "forloop.mk" line 14: x=one
-make: "forloop.mk" line 14: x="two and three"
-make: "forloop.mk" line 14: x=four
-make: "forloop.mk" line 14: x="five"
-make: "forloop.mk" line 20: x=-I/this
-make: "forloop.mk" line 20: x=-I"This or that"
-make: "forloop.mk" line 20: x=-Ithat
-make: "forloop.mk" line 20: x="-DTHIS=\"this and that\""
-make: "forloop.mk" line 27: cfl=-I/this -I"This or that" -Ithat "-DTHIS=\"this and that\""
-make: "forloop.mk" line 41: newline-item=(a)
-make: "forloop.mk" line 47: a=one b="two and three"
-make: "forloop.mk" line 47: a=four b="five"
-make: "forloop.mk" line 47: a=ONE b="TWO AND THREE"
-make: "forloop.mk" line 47: a=FOUR b="FIVE"
-We expect an error next:
-make: "forloop.mk" line 46: Wrong number of words (9) in .for substitution list with 2 variables
-make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
-OK
-exit status 0
diff --git a/contrib/bmake/unit-tests/forloop.mk b/contrib/bmake/unit-tests/forloop.mk
deleted file mode 100644
index cef05cbe4c61..000000000000
--- a/contrib/bmake/unit-tests/forloop.mk
+++ /dev/null
@@ -1,53 +0,0 @@
-# $NetBSD: forloop.mk,v 1.7 2020/11/03 17:37:57 rillig Exp $
-
-all: for-loop
-
-LIST= one "two and three" four "five"
-
-.if make(for-fail)
-for-fail:
-
-XTRA_LIST= xtra
-.else
-
-. for x in ${LIST}
-. info x=$x
-. endfor
-
-CFL= -I/this -I"This or that" -Ithat "-DTHIS=\"this and that\""
-cfl=
-. for x in ${CFL}
-. info x=$x
-. if empty(cfl)
-cfl= $x
-. else
-cfl+= $x
-. endif
-. endfor
-. info cfl=${cfl}
-
-. if ${cfl} != ${CFL}
-. error ${.newline}${cfl} != ${.newline}${CFL}
-. endif
-
-. for a b in ${EMPTY}
-. info a=$a b=$b
-. endfor
-
-# Since at least 1993, iteration stops at the first newline.
-# Back then, the .newline variable didn't exist, therefore it was unlikely
-# that a newline ever occurred.
-. for var in a${.newline}b${.newline}c
-. info newline-item=(${var})
-. endfor
-
-.endif # for-fail
-
-.for a b in ${LIST} ${LIST:tu} ${XTRA_LIST}
-. info a=$a b=$b
-.endfor
-
-for-loop:
- @echo We expect an error next:
- @(cd ${.CURDIR} && ${.MAKE} -f ${MAKEFILE} for-fail) && \
- { echo "Oops that should have failed!"; exit 1; } || echo OK
diff --git a/contrib/bmake/unit-tests/forsubst.exp b/contrib/bmake/unit-tests/forsubst.exp
deleted file mode 100644
index 0a98c00aff30..000000000000
--- a/contrib/bmake/unit-tests/forsubst.exp
+++ /dev/null
@@ -1,2 +0,0 @@
-.for with :S;... OK
-exit status 0
diff --git a/contrib/bmake/unit-tests/forsubst.mk b/contrib/bmake/unit-tests/forsubst.mk
deleted file mode 100644
index 9f293ab7f94e..000000000000
--- a/contrib/bmake/unit-tests/forsubst.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-# $NetBSD: forsubst.mk,v 1.3 2020/11/03 17:59:27 rillig Exp $
-#
-# The parser used to break dependency lines at ';' without regard for
-# substitution patterns. Back then, the first ';' was interpreted as the
-# separator between the dependency and its commands. This (perhaps coupled
-# with the new handling of .for variables in ${:U<value>...) caused
-# interesting results for lines like:
-#
-# .for file in ${LIST}
-# for-subst: ${file:S;^;${here}/;g}
-# .endfor
-#
-# See the commit to unit-tests/forsubst (without the .mk) from 2009-10-07.
-
-all: for-subst
-
-here := ${.PARSEDIR}
-# this should not run foul of the parser
-.for file in ${.PARSEFILE}
-for-subst: ${file:S;^;${here}/;g}
- @echo ".for with :S;... OK"
-.endfor
diff --git a/contrib/bmake/unit-tests/gnode-submake.exp b/contrib/bmake/unit-tests/gnode-submake.exp
index ea00e8d76c11..c9cfada91c69 100644
--- a/contrib/bmake/unit-tests/gnode-submake.exp
+++ b/contrib/bmake/unit-tests/gnode-submake.exp
@@ -1,4 +1,4 @@
-#*** Input graph:
+#*** Begin input graph for pass 1 in <curdir>:
# all, unmade, type OP_DEPENDS, flags none
# makeinfo, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none
# make-index, unmade, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none
diff --git a/contrib/bmake/unit-tests/hanoi-include.mk b/contrib/bmake/unit-tests/hanoi-include.mk
index 3b9438bf2169..f243af83d1df 100644
--- a/contrib/bmake/unit-tests/hanoi-include.mk
+++ b/contrib/bmake/unit-tests/hanoi-include.mk
@@ -1,41 +1,49 @@
-# $NetBSD: hanoi-include.mk,v 1.2 2022/01/08 22:13:43 rillig Exp $
+# $NetBSD: hanoi-include.mk,v 1.5 2023/10/19 18:24:33 rillig Exp $
#
-# Implements the Towers of Hanoi puzzle, thereby demonstrating a bunch of
-# more or less useful programming techniques:
+# Implements the Towers of Hanoi puzzle, demonstrating a bunch of more or less
+# useful programming techniques:
#
-# * default assignment using the ?= assignment operator
-# * 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
-# * on-the-fly variable assignment expressions using the ::= modifier
+# * default assignment using the ?= assignment operator
+# * including the same file recursively (rather unusual)
+# * extracting the current value of a variable using the .for loop
+# * using the :: dependency operator for adding commands to a target
+# * on-the-fly variable assignment expressions using the ::= modifier
#
# usage:
-# env N=3 make -f hanoi-include.mk
-# endless loop:
-# make -f hanoi-include.mk N=3
+# env N=3 make -r -f hanoi-include.mk
+#
+# Specifying N in the command line instead of in the environment would produce
+# an endless loop, since variables from the command line cannot be overridden
+# by global variables:
+# make -r -f hanoi-include.mk N=3
N?= 5 # Move this number of disks ...
FROM?= A # ... from this stack ...
VIA?= B # ... via this stack ...
TO?= C # ... to this stack.
-.if $N == 1
+# Since make has no built-in arithmetic functions, convert N to a list of
+# words and use the built-in word counting instead.
+.if ${N:[#]} == 1
+N:= count ${:U:${:Urange=$N}} # 'count' + one word for every disk
+.endif
+
+.if ${N:[#]} == 2
. for from to in ${FROM} ${TO}
all::
@echo "Move the upper disk from stack ${from} to stack ${to}."
. endfor
.else
-_:= ${N::!=expr $N - 1} ${TMP::=${VIA}} ${VIA::=${TO}} ${TO::=${TMP}}
+_:= ${N::=${N:[1..-2]}} ${TMP::=${VIA}} ${VIA::=${TO}} ${TO::=${TMP}}
. include "${.PARSEDIR}/${.PARSEFILE}"
-_:= ${N::!=expr $N + 1} ${TMP::=${VIA}} ${VIA::=${TO}} ${TO::=${TMP}}
+_:= ${N::+=D} ${TMP::=${VIA}} ${VIA::=${TO}} ${TO::=${TMP}}
. for from to in ${FROM} ${TO}
all::
@echo "Move the upper disk from stack ${from} to stack ${to}."
. endfor
-_:= ${N::!=expr $N - 1} ${TMP::=${VIA}} ${VIA::=${FROM}} ${FROM::=${TMP}}
+_:= ${N::=${N:[1..-2]}} ${TMP::=${VIA}} ${VIA::=${FROM}} ${FROM::=${TMP}}
. include "${.PARSEDIR}/${.PARSEFILE}"
-_:= ${N::!=expr $N + 1} ${TMP::=${VIA}} ${VIA::=${FROM}} ${FROM::=${TMP}}
+_:= ${N::+=D} ${TMP::=${VIA}} ${VIA::=${FROM}} ${FROM::=${TMP}}
.endif
diff --git a/contrib/bmake/unit-tests/include-main.exp b/contrib/bmake/unit-tests/include-main.exp
index e677826373c1..f7587982d9b5 100644
--- a/contrib/bmake/unit-tests/include-main.exp
+++ b/contrib/bmake/unit-tests/include-main.exp
@@ -1,17 +1,17 @@
-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
-Parsing line 5: . info subsub-ok
-make: "include-subsub.mk" line 5: subsub-ok
- 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
+make: include-main.mk:15: main-before-ok
+make: include-main.mk:23: main-before-for-ok
+make: include-sub.inc:4: sub-before-ok
+make: include-sub.inc:14: sub-before-for-ok
+Parsing include-subsub.inc:5: . info subsub-ok
+make: include-subsub.inc:5: subsub-ok
+ in .for loop from include-sub.inc:31 with i = include
+ in .for loop from include-sub.inc:30 with i = nested
+ in .for loop from include-sub.inc:29 with i = deeply
+ in include-main.mk:29
+Parsing include-subsub.inc: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
-make: "include-main.mk" line 30: main-after-ok
-make: "include-main.mk" line 37: main-after-for-ok
+make: include-sub.inc:38: sub-after-ok
+make: include-sub.inc:45: sub-after-for-ok
+make: include-main.mk:33: main-after-ok
+make: include-main.mk:41: main-after-for-ok
exit status 0
diff --git a/contrib/bmake/unit-tests/include-main.mk b/contrib/bmake/unit-tests/include-main.mk
index 9a4c6630506b..373bbdea1721 100644
--- a/contrib/bmake/unit-tests/include-main.mk
+++ b/contrib/bmake/unit-tests/include-main.mk
@@ -1,4 +1,4 @@
-# $NetBSD: include-main.mk,v 1.7 2022/01/08 23:41:43 rillig Exp $
+# $NetBSD: include-main.mk,v 1.9 2023/06/01 20:56:35 rillig Exp $
#
# Until 2020-09-05, the .INCLUDEDFROMFILE magic variable did not behave
# as described in the manual page.
@@ -11,6 +11,7 @@
# properly handle nested includes and even .for loops.
.if !defined(.INCLUDEDFROMFILE)
+# expect+1: main-before-ok
. info main-before-ok
.else
. warning main-before-fail(${.INCLUDEDFROMFILE})
@@ -18,15 +19,17 @@
.for i in once
. if !defined(.INCLUDEDFROMFILE)
+# expect+1: main-before-for-ok
. info main-before-for-ok
. else
. warning main-before-for-fail(${.INCLUDEDFROMFILE})
. endif
.endfor
-.include "include-sub.mk"
+.include "include-sub.inc"
.if !defined(.INCLUDEDFROMFILE)
+# expect+1: main-after-ok
. info main-after-ok
.else
. warning main-after-fail(${.INCLUDEDFROMFILE})
@@ -34,6 +37,7 @@
.for i in once
. if !defined(.INCLUDEDFROMFILE)
+# expect+1: main-after-for-ok
. info main-after-for-ok
. else
. warning main-after-for-fail(${.INCLUDEDFROMFILE})
diff --git a/contrib/bmake/unit-tests/include-sub.mk b/contrib/bmake/unit-tests/include-sub.inc
index 57d2aafe9d1d..f26f14c9d84f 100644
--- a/contrib/bmake/unit-tests/include-sub.mk
+++ b/contrib/bmake/unit-tests/include-sub.inc
@@ -1,4 +1,4 @@
-# $NetBSD: include-sub.mk,v 1.9 2022/01/08 23:41:43 rillig Exp $
+# $NetBSD: include-sub.inc,v 1.1 2023/01/19 23:26:14 rillig Exp $
.if ${.INCLUDEDFROMFILE} == "include-main.mk"
. info sub-before-ok
@@ -29,7 +29,7 @@
.for i in deeply
. for i in nested
. for i in include
-.include "include-subsub.mk"
+.include "include-subsub.inc"
. endfor
. endfor
.endfor
diff --git a/contrib/bmake/unit-tests/include-subsub.inc b/contrib/bmake/unit-tests/include-subsub.inc
new file mode 100644
index 000000000000..79a6a3770090
--- /dev/null
+++ b/contrib/bmake/unit-tests/include-subsub.inc
@@ -0,0 +1,9 @@
+# $NetBSD: include-subsub.inc,v 1.1 2023/01/19 23:26:14 rillig Exp $
+
+.if ${.INCLUDEDFROMFILE} == "include-sub.inc"
+.MAKEFLAGS: -dp
+. info subsub-ok
+.MAKEFLAGS: -d0
+.else
+. warning subsub-fail(${.INCLUDEDFROMFILE})
+.endif
diff --git a/contrib/bmake/unit-tests/include-subsub.mk b/contrib/bmake/unit-tests/include-subsub.mk
deleted file mode 100644
index 476d75f79556..000000000000
--- a/contrib/bmake/unit-tests/include-subsub.mk
+++ /dev/null
@@ -1,9 +0,0 @@
-# $NetBSD: include-subsub.mk,v 1.4 2021/01/26 23:44:56 rillig Exp $
-
-.if ${.INCLUDEDFROMFILE} == "include-sub.mk"
-.MAKEFLAGS: -dp
-. info subsub-ok
-.MAKEFLAGS: -d0
-.else
-. warning subsub-fail(${.INCLUDEDFROMFILE})
-.endif
diff --git a/contrib/bmake/unit-tests/job-output-null.exp b/contrib/bmake/unit-tests/job-output-null.exp
index 631d4862af44..628ec54a1a6b 100644
--- a/contrib/bmake/unit-tests/job-output-null.exp
+++ b/contrib/bmake/unit-tests/job-output-null.exp
@@ -1,4 +1,6 @@
-1
-2a
+1 trailing
+2a trailing
+2b trailing
+2c trailing
3a without newline, 3b without newline.
exit status 0
diff --git a/contrib/bmake/unit-tests/job-output-null.mk b/contrib/bmake/unit-tests/job-output-null.mk
index 1efd9c667980..04786dba4096 100644
--- a/contrib/bmake/unit-tests/job-output-null.mk
+++ b/contrib/bmake/unit-tests/job-output-null.mk
@@ -1,11 +1,11 @@
-# $NetBSD: job-output-null.mk,v 1.3 2021/09/12 10:26:49 rillig Exp $
+# $NetBSD: job-output-null.mk,v 1.4 2022/09/03 08:03:27 rillig Exp $
#
# Test how null bytes in the output of a command are handled. Make processes
# them using null-terminated strings, which may cut off some of the output.
#
-# As of 2021-04-15, make handles null bytes from the child process
-# inconsistently. It's an edge case though since typically the child
-# processes output text.
+# Before job.c 1.454 from 2022-09-03, make handled null bytes in the output
+# from the child process inconsistently. It's an edge case though since
+# typically the child processes output text.
# Note: The printf commands used in this test must only use a single format
# string, without parameters. This is because it is implementation-dependent
@@ -16,30 +16,40 @@
# NetBSD /bin/ksh 3 x write("fmt") (via /bin/printf)
# Bash 5 3 x write("fmt")
#
-# In the latter case the output may arrive in parts, which in this test makes
-# a crucial difference since the outcome of the test depends on whether there
-# is a '\n' in each of the blocks from the output.
+# In the latter case the output may arrive in 1 to 3 parts, depending on the
+# exact timing, which in this test makes a crucial difference since before
+# job.c 1.454 from 2022-09-03, the outcome of the test depended on whether
+# there was a '\n' in each of the blocks from the output. Depending on the
+# exact timing, the output of that test varied, its possible values were '2a',
+# '2a 2b', '2a 2c', '2a 2b 2c'.
.MAKEFLAGS: -j1 # force jobs mode
all: .PHONY
- # The null byte from the command output is kept as-is.
- # See CollectOutput, which looks like it intended to replace these
- # null bytes with simple spaces.
+ # The null byte from the command output is replaced with a single
+ # space by CollectOutput.
@printf '1\0trailing\n'
+ # expect: 1 trailing
# Give the parent process a chance to see the above output, but not
# yet the output from the next printf command.
@sleep 1
- # All null bytes from the command output are kept as-is.
+ # Each null byte from the command output is replaced with a single
+ # space.
@printf '2a\0trailing\n''2b\0trailing\n''2c\0trailing\n'
+ # expect: 2a trailing
+ # expect: 2b trailing
+ # expect: 2c trailing
@sleep 1
- # The null bytes are replaced with spaces since they are not followed
- # by a newline.
+ # Each null byte from the command output is replaced with a single
+ # space. Because there is no trailing newline in the output, these
+ # null bytes were replaced with spaces even before job.c 1.454 from
+ # 2022-09-03, unlike in the cases above.
#
# The three null bytes in a row test whether this output is
# compressed to a single space like in DebugFailedTarget. It isn't.
@printf '3a\0without\0\0\0newline, 3b\0without\0\0\0newline.'
+ # expect: 3a without newline, 3b without newline.
diff --git a/contrib/bmake/unit-tests/job-output.exp b/contrib/bmake/unit-tests/job-output.exp
new file mode 100644
index 000000000000..1891021bf3e7
--- /dev/null
+++ b/contrib/bmake/unit-tests/job-output.exp
@@ -0,0 +1,13 @@
+begin empty-lines
+
+
+end empty-lines
+begin stdout-and-stderr
+only stdout:
+This is stdout.
+This is stderr.
+only stderr:
+end stdout-and-stderr
+This is stdout.
+This is stderr.
+exit status 0
diff --git a/contrib/bmake/unit-tests/job-output.mk b/contrib/bmake/unit-tests/job-output.mk
new file mode 100644
index 000000000000..ae4708a5b2f8
--- /dev/null
+++ b/contrib/bmake/unit-tests/job-output.mk
@@ -0,0 +1,41 @@
+# $NetBSD: job-output.mk,v 1.2 2025/06/13 06:13:20 rillig Exp $
+#
+# Tests for handling the output in parallel mode.
+
+all: .PHONY
+ @${MAKE} -f ${MAKEFILE} -j1 empty-lines
+ @${MAKE} -f ${MAKEFILE} -j1 stdout-and-stderr
+ @${MAKE} -f ${MAKEFILE} -j1 echo-on-stdout-and-stderr
+
+# By sleeping for a second, the output of the child process is written byte
+# by byte, to be consumed in small pieces by make. No matter what the chunk
+# size is, the empty lines must not be discarded.
+empty-lines: .PHONY
+ @echo begin $@
+ @sleep 1
+ @echo
+ @sleep 1
+ @echo
+ @sleep 1
+ @echo end $@
+
+# In parallel mode, both stdout and stderr from the child process are
+# collected in a local buffer and then written to make's stdout.
+#
+# expect: begin stdout-and-stderr
+# expect: only stdout:
+# expect: This is stdout.
+# expect: This is stderr.
+# expect: only stderr:
+# expect: end stdout-and-stderr
+stdout-and-stderr:
+ @echo begin $@
+ @echo only stdout:
+ @${MAKE} -f ${MAKEFILE} echo-on-stdout-and-stderr 2>/dev/null
+ @echo only stderr:
+ @${MAKE} -f ${MAKEFILE} echo-on-stdout-and-stderr 1>/dev/null
+ @echo end $@
+
+echo-on-stdout-and-stderr: .PHONY
+ @echo This is stdout.
+ @echo This is stderr. 1>&2
diff --git a/contrib/bmake/unit-tests/jobs-empty-commands-error.exp b/contrib/bmake/unit-tests/jobs-empty-commands-error.exp
index 1639425d9013..22acf79be242 100644
--- a/contrib/bmake/unit-tests/jobs-empty-commands-error.exp
+++ b/contrib/bmake/unit-tests/jobs-empty-commands-error.exp
@@ -1,5 +1,5 @@
: 'Making existing-target out of nothing.'
make: don't know how to make nonexistent-target (continuing)
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 2
diff --git a/contrib/bmake/unit-tests/jobs-error-indirect.exp b/contrib/bmake/unit-tests/jobs-error-indirect.exp
index 5c5a3801f4f6..989d80c99cee 100644
--- a/contrib/bmake/unit-tests/jobs-error-indirect.exp
+++ b/contrib/bmake/unit-tests/jobs-error-indirect.exp
@@ -1,8 +1,8 @@
false
*** [indirect] Error code 1
-make: stopped in unit-tests
-1 error
+make: stopped making "all" in unit-tests
+make: 1 error
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 2
diff --git a/contrib/bmake/unit-tests/jobs-error-nested-make.exp b/contrib/bmake/unit-tests/jobs-error-nested-make.exp
index 88c32ab8d1f6..cd9c8d17336f 100644
--- a/contrib/bmake/unit-tests/jobs-error-nested-make.exp
+++ b/contrib/bmake/unit-tests/jobs-error-nested-make.exp
@@ -2,10 +2,10 @@ make -f jobs-error-nested-make.mk nested
false
*** [nested] Error code 1
-make: stopped in unit-tests
-1 error
+make: stopped making "nested" in unit-tests
+make: 1 error
-make: stopped in unit-tests
+make: stopped making "nested" in unit-tests
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 2
diff --git a/contrib/bmake/unit-tests/jobs-error-nested.exp b/contrib/bmake/unit-tests/jobs-error-nested.exp
index f96b5d016777..5f5f8029ffd9 100644
--- a/contrib/bmake/unit-tests/jobs-error-nested.exp
+++ b/contrib/bmake/unit-tests/jobs-error-nested.exp
@@ -2,14 +2,14 @@ make -f jobs-error-nested.mk nested
false
*** [nested] Error code 1
-make: stopped in unit-tests
-1 error
+make: stopped making "nested" in unit-tests
+make: 1 error
-make: stopped in unit-tests
+make: stopped making "nested" in unit-tests
*** [all] Error code 2
-make: stopped in unit-tests
-1 error
+make: stopped making "all" in unit-tests
+make: 1 error
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 2
diff --git a/contrib/bmake/unit-tests/lint.exp b/contrib/bmake/unit-tests/lint.exp
index db2290c040cd..61188d6d5c29 100755
--- a/contrib/bmake/unit-tests/lint.exp
+++ b/contrib/bmake/unit-tests/lint.exp
@@ -1,4 +1,5 @@
-make: In the :@ modifier of "VAR", the variable name "${:Ubar:S,b,v,}" must not contain a dollar
-y@:Q}
-xvaluey
+make: In the :@ modifier, the variable name "${:Ubar:S,b,v,}" must not contain a dollar
+ while evaluating variable "VAR" with value "value"
+ in command "@echo ${VAR:Uvalue:@${:Ubar:S,b,v,}@x${var}y@:Q}"
+ in target "mod-loop-varname"
exit status 2
diff --git a/contrib/bmake/unit-tests/lint.mk b/contrib/bmake/unit-tests/lint.mk
index 5db417639d0b..431143c644ee 100755
--- a/contrib/bmake/unit-tests/lint.mk
+++ b/contrib/bmake/unit-tests/lint.mk
@@ -1,4 +1,4 @@
-# $NetBSD: lint.mk,v 1.4 2021/01/30 13:50:18 rillig Exp $
+# $NetBSD: lint.mk,v 1.5 2023/11/19 21:47:52 rillig Exp $
#
# Demonstrates stricter checks that are only enabled in lint mode, using the
# option -dL.
@@ -6,7 +6,7 @@
# Before main.c 1.421 from 2020-11-01, make exited successfully even though
# the error message had been issued as PARSE_FATAL. This was because back
# then, make checked for parse errors only after parsing each top-level
-# makefile, in Parse_File. After that, when expanding variable expressions
+# makefile, in Parse_File. After that, when expanding expressions
# in shell commands, the parse errors were not checked again.
# Ouch: as of 2020-08-03, the variable is malformed and parsing stops
diff --git a/contrib/bmake/unit-tests/make-exported.mk b/contrib/bmake/unit-tests/make-exported.mk
index 58cb15183b8d..363ea2733a47 100755
--- a/contrib/bmake/unit-tests/make-exported.mk
+++ b/contrib/bmake/unit-tests/make-exported.mk
@@ -1,4 +1,4 @@
-# $NetBSD: make-exported.mk,v 1.6 2020/10/05 19:27:48 rillig Exp $
+# $NetBSD: make-exported.mk,v 1.7 2022/09/09 18:36:15 sjg Exp $
#
# As of 2020-08-09, the code in Var_Export is shared between the .export
# directive and the .MAKE.EXPORTED variable. This leads to non-obvious
@@ -22,4 +22,4 @@ UT_VAR= ${UNEXPANDED}
.MAKE.EXPORTED= -literal UT_VAR
all:
- @env | sort | egrep '^UT_|make-exported-value' || true
+ @env | sort | ${EGREP} '^UT_|make-exported-value' || true
diff --git a/contrib/bmake/unit-tests/meta-cmd-cmp.exp b/contrib/bmake/unit-tests/meta-cmd-cmp.exp
index dc63da3b346b..c925e31d0489 100644
--- a/contrib/bmake/unit-tests/meta-cmd-cmp.exp
+++ b/contrib/bmake/unit-tests/meta-cmd-cmp.exp
@@ -7,27 +7,27 @@ Skipping meta for .END: .SPECIAL
two:
`.meta-cmd-cmp.cmp' is up to date.
`.meta-cmd-cmp.nocmp' is up to date.
-.meta-cmd-cmp.cmp2.meta: 3: cannot compare command using .OODATE
+.meta-cmd-cmp.cmp2.meta:3: cannot compare command using .OODATE
`.meta-cmd-cmp.cmp2' is up to date.
Skipping meta for .END: .SPECIAL
change1:
-.meta-cmd-cmp.cmp.meta: 2: a build command has changed
+.meta-cmd-cmp.cmp.meta:2: a build command has changed
@echo FLAGS= > .meta-cmd-cmp.cmp
vs
@echo FLAGS=changed > .meta-cmd-cmp.cmp
Building .meta-cmd-cmp.cmp
`.meta-cmd-cmp.nocmp' is up to date.
-.meta-cmd-cmp.cmp2.meta: 3: cannot compare command using .OODATE
+.meta-cmd-cmp.cmp2.meta:3: cannot compare command using .OODATE
`.meta-cmd-cmp.cmp2' is up to date.
Skipping meta for .END: .SPECIAL
change2:
-.meta-cmd-cmp.cmp.meta: 2: a build command has changed
+.meta-cmd-cmp.cmp.meta:2: a build command has changed
@echo FLAGS=changed > .meta-cmd-cmp.cmp
vs
@echo FLAGS= > .meta-cmd-cmp.cmp
Building .meta-cmd-cmp.cmp
`.meta-cmd-cmp.nocmp' is up to date.
-.meta-cmd-cmp.cmp2.meta: 2: a build command has changed
+.meta-cmd-cmp.cmp2.meta:2: a build command has changed
@echo FLAGS2= > .meta-cmd-cmp.cmp2
vs
@echo FLAGS2=changed > .meta-cmd-cmp.cmp2
@@ -38,7 +38,7 @@ filter0:
Building .meta-cmd-cmp.filter
Skipping meta for .END: .SPECIAL
filter1:
-.meta-cmd-cmp.filter.meta: 2: a build command has changed
+.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
diff --git a/contrib/bmake/unit-tests/meta-ignore.inc b/contrib/bmake/unit-tests/meta-ignore.inc
new file mode 100644
index 000000000000..ed74f4d79017
--- /dev/null
+++ b/contrib/bmake/unit-tests/meta-ignore.inc
@@ -0,0 +1,63 @@
+# $NetBSD: meta-ignore.inc,v 1.2 2023/02/25 19:30:32 sjg Exp $
+
+# common logic for testing .MAKE.META.IGNORE_*
+
+# we want a directory outside of .OBJDIR to drop a file
+# that our meta file refers to.
+# Note: these tests will not work if TMPDIR is /tmp or /var/tmp
+# or a subdir thereof
+IGNORE:= ${TMPDIR}/ignore
+OBJ:= ${TMPDIR}/obj
+
+# this is always ignored so make sure it isn't used above
+TMPDIR= /tmp/nothanks
+
+all: one two three
+
+setup:
+ @mkdir -p ${IGNORE} ${OBJ}
+ @echo > ${IGNORE}/check
+ @rm -f ${OBJ}/check-ignore
+
+makefile:= ${.INCLUDEDFROMDIR}/${.INCLUDEDFROMFILE}
+TEST:= ${.INCLUDEDFROMFILE:R:C,.*meta-,,:S,-,_,g:tu}
+
+DESC.one= Initialize check-ignore.meta
+DESC.two= Use .MAKE.META.${TEST} - check-ignore is up to date
+DESC.three= Skip .MAKE.META.${TEST} - check-ignore is out of date
+
+# just in case someone runs us with -jN
+.ORDER: one two three
+one two three: .MAKE setup
+ @echo "${DESC.${.TARGET}}"; \
+ ${MAKE} -C ${.CURDIR} -f ${makefile} check-ignore parent=${.TARGET}
+
+.if make(check-ignore)
+.MAKEFLAGS: -dM
+.MAKE.MODE = meta verbose silent=yes
+.OBJDIR: ${OBJ}
+.if ${parent} == "two"
+.if ${TEST} == "IGNORE_PATHS"
+# this is a prefix list - any path that matches
+# one of these prefixes will be ignored
+.MAKE.META.IGNORE_PATHS = ${IGNORE}
+.elif ${TEST} == "IGNORE_PATTERNS"
+# more flexible but more expensive
+# this example is equivalent to M*/ignore/*
+# a match means ignore
+.MAKE.META.IGNORE_PATTERNS = */ignore/*
+.elif ${TEST} == "IGNORE_FILTER"
+# this is the most flexible, but also most expensive
+# if this expands to nothing - ignore the path
+.MAKE.META.IGNORE_FILTER = N${IGNORE}/*
+.endif
+.endif
+
+# : < just reads from ${IGNORE}/check
+# so that our filemon trace will have a reference to it
+# we ensure it is always newer than the target.
+check-ignore: .META .NOPATH
+ @: < ${IGNORE}/check > ${.TARGET}
+ @sleep 1; echo ${.TARGET} > ${IGNORE}/check
+
+.endif
diff --git a/contrib/bmake/unit-tests/moderrs.exp b/contrib/bmake/unit-tests/moderrs.exp
index 9d8bd308c36c..4758294f0993 100644
--- a/contrib/bmake/unit-tests/moderrs.exp
+++ b/contrib/bmake/unit-tests/moderrs.exp
@@ -1,137 +1,173 @@
-mod-unknown-direct:
-want: Unknown modifier 'Z'
-make: Unknown modifier "Z"
-VAR:Z=before--after
-
-mod-unknown-indirect:
-want: Unknown modifier 'Z'
-make: Unknown modifier "Z"
-VAR:Z=before-inner}-after
-
-unclosed-direct:
-want: Unclosed variable expression, expecting '}' for modifier "S,V,v," of variable "VAR" with value "Thevariable"
-make: Unclosed variable expression, expecting '}' for modifier "S,V,v," of variable "VAR" with value "Thevariable"
-VAR:S,V,v,=Thevariable
-
-unclosed-indirect:
-want: Unclosed variable expression after indirect modifier, expecting '}' for variable "VAR"
-make: Unclosed variable expression after indirect modifier, expecting '}' for variable "VAR"
-VAR:S,V,v,=Thevariable
-
-unfinished-indirect:
-want: Unfinished modifier for VAR (',' missing)
-make: Unfinished modifier for "VAR" (',' missing)
-VAR:S,V,v=
-
-unfinished-loop:
-want: Unfinished modifier for UNDEF ('@' missing)
-make: Unfinished modifier for "UNDEF" ('@' missing)
-
-want: Unfinished modifier for UNDEF ('@' missing)
-make: Unfinished modifier for "UNDEF" ('@' missing)
-
+make: Unknown modifier ":Z"
+ while evaluating variable "VAR" with value "TheVariable"
+ in command "@echo 'VAR:Z=before-${VAR:Z}-after'"
+ in target "mod-unknown-direct"
+make: Unknown modifier ":Z"
+ while evaluating indirect modifiers "Z"
+ while evaluating variable "VAR" with value "TheVariable"
+ in command "@echo 'VAR:${MOD_UNKN}=before-${VAR:${MOD_UNKN}:inner}-after'"
+ in target "mod-unknown-indirect"
+make: Unclosed expression, expecting "}" for modifier "S,V,v,"
+ while evaluating variable "VAR" with value "Thevariable"
+ in command "@echo VAR:S,V,v,=${VAR:S,V,v,"
+ in target "unclosed-direct"
+make: Unclosed expression after indirect modifier, expecting "}"
+ while evaluating variable "VAR" with value "Thevariable"
+ in command "@echo VAR:${MOD_TERM},=${VAR:${MOD_S}"
+ in target "unclosed-indirect"
+make: Unfinished modifier after "v", expecting ","
+ while evaluating indirect modifiers "S,V,v"
+ while evaluating variable "VAR" with value "TheVariable"
+ in command "-@echo "VAR:${MOD_TERM}=${VAR:${MOD_TERM}}""
+ in target "unfinished-indirect"
+make: Unfinished modifier after "var}", expecting "@"
+ while evaluating variable "UNDEF" with value "1 2 3"
+ in command "@echo ${UNDEF:U1 2 3:@var}"
+ in target "unfinished-loop-1"
+make: Unfinished modifier after "...}", expecting "@"
+ while evaluating variable "UNDEF" with value "1 2 3"
+ in command "@echo ${UNDEF:U1 2 3:@var@...}"
+ in target "unfinished-loop-2"
1 2 3
-
-loop-close:
-make: Unclosed variable expression, expecting '}' for modifier "@var@${var}}...@" of variable "UNDEF" with value "1}... 2}... 3}..."
+make: Unclosed expression, expecting "}" for modifier "@var@${var}}...@"
+ while evaluating variable "UNDEF" with value "1}... 2}... 3}..."
+ in command "@echo ${UNDEF:U1 2 3:@var@${var}}...@"
+ in target "loop-close-1"
1}... 2}... 3}...
-1}... 2}... 3}...
-
-words:
-want: Unfinished modifier for UNDEF (']' missing)
-make: Unfinished modifier for "UNDEF" (']' missing)
-
-want: Unfinished modifier for UNDEF (']' missing)
-make: Unfinished modifier for "UNDEF" (']' missing)
-
+make: Unfinished modifier after "}", expecting "]"
+ while evaluating variable "UNDEF" with value "1 2 3"
+ in command "@echo ${UNDEF:U1 2 3:[}"
+ in target "words-1"
+make: Unfinished modifier after "#}", expecting "]"
+ while evaluating variable "UNDEF" with value "1 2 3"
+ in command "@echo ${UNDEF:U1 2 3:[#}"
+ in target "words-2"
13=
-make: Bad modifier ":[123451234512345123451234512345]" for variable "UNDEF"
-12345=S,^ok,:S,^3ok,}
-
-exclam:
-want: Unfinished modifier for VARNAME ('!' missing)
-make: Unfinished modifier for "VARNAME" ('!' missing)
-
-want: Unfinished modifier for ! ('!' missing)
-make: Unfinished modifier for "!" ('!' missing)
-
-
-mod-subst-delimiter:
-make: Missing delimiter for modifier ':S'
-1:
-make: Unfinished modifier for "VAR" (',' missing)
-2:
-make: Unfinished modifier for "VAR" (',' missing)
-3:
-make: Unfinished modifier for "VAR" (',' missing)
-4:
-make: Unfinished modifier for "VAR" (',' missing)
-5:
-make: Unclosed variable expression, expecting '}' for modifier "S,from,to," of variable "VAR" with value "TheVariable"
-6: TheVariable
+make: Invalid modifier ":[123451234512345123451234512345]"
+ while evaluating variable "UNDEF" with value "1 2 3"
+ in command "@echo 12345=${UNDEF:U1 2 3:[123451234512345123451234512345]:S,^$,ok,:S,^3$,ok,}"
+ in target "words-3"
+make: Unfinished modifier after "echo}", expecting "!"
+ while evaluating variable "VARNAME" with value ""
+ in command "@echo ${VARNAME:!echo}"
+ in target "exclam-1"
+make: Unfinished modifier after "=exclam}", expecting "!"
+ while evaluating variable "!" with value "!"
+ in command "@echo ${!:L:!=exclam}"
+ in target "exclam-2"
+make: Missing delimiter for modifier ":S"
+ while evaluating variable "VAR" with value "TheVariable"
+ in command "@echo 1: ${VAR:S"
+ in target "mod-subst-delimiter-1"
+make: Unfinished modifier after "", expecting ","
+ while evaluating variable "VAR" with value "TheVariable"
+ in command "@echo 2: ${VAR:S,"
+ in target "mod-subst-delimiter-2"
+make: Unfinished modifier after "from", expecting ","
+ while evaluating variable "VAR" with value "TheVariable"
+ in command "@echo 3: ${VAR:S,from"
+ in target "mod-subst-delimiter-3"
+make: Unfinished modifier after "", expecting ","
+ while evaluating variable "VAR" with value "TheVariable"
+ in command "@echo 4: ${VAR:S,from,"
+ in target "mod-subst-delimiter-4"
+make: Unfinished modifier after "to", expecting ","
+ while evaluating variable "VAR" with value "TheVariable"
+ in command "@echo 5: ${VAR:S,from,to"
+ in target "mod-subst-delimiter-5"
+make: Unclosed expression, expecting "}" for modifier "S,from,to,"
+ while evaluating variable "VAR" with value "TheVariable"
+ in command "@echo 6: ${VAR:S,from,to,"
+ in target "mod-subst-delimiter-6"
7: TheVariable
-
-mod-regex-delimiter:
-make: Missing delimiter for :C modifier
-1:
-make: Unfinished modifier for "VAR" (',' missing)
-2:
-make: Unfinished modifier for "VAR" (',' missing)
-3:
-make: Unfinished modifier for "VAR" (',' missing)
-4:
-make: Unfinished modifier for "VAR" (',' missing)
-5:
-make: Unclosed variable expression, expecting '}' for modifier "C,from,to," of variable "VAR" with value "TheVariable"
-6: TheVariable
+make: Missing delimiter for modifier ":C"
+ while evaluating variable "VAR" with value "TheVariable"
+ in command "@echo 1: ${VAR:C"
+ in target "mod-regex-delimiter-1"
+make: Unfinished modifier after "", expecting ","
+ while evaluating variable "VAR" with value "TheVariable"
+ in command "@echo 2: ${VAR:C,"
+ in target "mod-regex-delimiter-2"
+make: Unfinished modifier after "from", expecting ","
+ while evaluating variable "VAR" with value "TheVariable"
+ in command "@echo 3: ${VAR:C,from"
+ in target "mod-regex-delimiter-3"
+make: Unfinished modifier after "", expecting ","
+ while evaluating variable "VAR" with value "TheVariable"
+ in command "@echo 4: ${VAR:C,from,"
+ in target "mod-regex-delimiter-4"
+make: Unfinished modifier after "to", expecting ","
+ while evaluating variable "VAR" with value "TheVariable"
+ in command "@echo 5: ${VAR:C,from,to"
+ in target "mod-regex-delimiter-5"
+make: Unclosed expression, expecting "}" for modifier "C,from,to,"
+ while evaluating variable "VAR" with value "TheVariable"
+ in command "@echo 6: ${VAR:C,from,to,"
+ in target "mod-regex-delimiter-6"
7: TheVariable
-
-mod-ts-parse:
112358132134
15152535558513521534
-make: Bad modifier ":ts\65oct" for variable "FIB"
-65oct}
-make: Bad modifier ":ts\65oct" for variable ""
-65oct}
-make: Bad modifier ":tsxy" for variable "FIB"
-xy}
-
-mod-t-parse:
-make: Bad modifier ":t" for variable "FIB"
-
-make: Bad modifier ":txy" for variable "FIB"
-y}
-make: Bad modifier ":t" for variable "FIB"
-
-make: Bad modifier ":t" for variable "FIB"
-M*}
-
-mod-ifelse-parse:
-make: Unfinished modifier for "FIB" (':' missing)
-
-make: Unfinished modifier for "FIB" (':' missing)
-
-make: Unfinished modifier for "FIB" ('}' missing)
-
-make: Unfinished modifier for "FIB" ('}' missing)
-
+make: Unknown modifier ":ts\65oct"
+ while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34"
+ in command "@echo ${FIB:ts\65oct} # bad modifier"
+ in target "mod-ts-parse-3"
+make: Unknown modifier ":ts\65oct"
+ while evaluating "${:U${FIB}:ts\65oct} # bad modifier, variable name is """ with value "1 1 2 3 5 8 13 21 34"
+ in command "@echo ${:U${FIB}:ts\65oct} # bad modifier, variable name is """
+ in target "mod-ts-parse-4"
+make: Unknown modifier ":tsxy"
+ while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34"
+ in command "@echo ${FIB:tsxy} # modifier too long"
+ in target "mod-ts-parse-5"
+make: Unknown modifier ":t"
+ while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34"
+ in command "@echo ${FIB:t"
+ in target "mod-t-parse-1"
+make: Unknown modifier ":txy"
+ while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34"
+ in command "@echo ${FIB:txy}"
+ in target "mod-t-parse-2"
+make: Unknown modifier ":t"
+ while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34"
+ in command "@echo ${FIB:t}"
+ in target "mod-t-parse-3"
+make: Unknown modifier ":t"
+ while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34"
+ in command "@echo ${FIB:t:M*}"
+ in target "mod-t-parse-4"
+make: Unfinished modifier after "", expecting ":"
+ while evaluating then-branch of condition "FIB"
+ in command "@echo ${FIB:?"
+ in target "mod-ifelse-parse-1"
+make: Unfinished modifier after "then", expecting ":"
+ while evaluating then-branch of condition "FIB"
+ in command "@echo ${FIB:?then"
+ in target "mod-ifelse-parse-2"
+make: Unfinished modifier after "", expecting "}"
+ while evaluating else-branch of condition "FIB"
+ in command "@echo ${FIB:?then:"
+ in target "mod-ifelse-parse-3"
+make: Unfinished modifier after "else", expecting "}"
+ while evaluating else-branch of condition "FIB"
+ in command "@echo ${FIB:?then:else"
+ in target "mod-ifelse-parse-4"
then
-
-mod-remember-parse:
1 1 2 3 5 8 13 21 34
-make: Unknown modifier "__"
-
-
-mod-sysv-parse:
-make: Unknown modifier "3"
-make: Unclosed variable expression, expecting '}' for modifier "3" of variable "FIB" with value ""
-
-make: Unknown modifier "3="
-make: Unclosed variable expression, expecting '}' for modifier "3=" of variable "FIB" with value ""
-
-make: Unknown modifier "3=x3"
-make: Unclosed variable expression, expecting '}' for modifier "3=x3" of variable "FIB" with value ""
-
+make: Unknown modifier ":__"
+ while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34"
+ in command "@echo ${FIB:__} # modifier name too long"
+ in target "mod-remember-parse"
+make: Unknown modifier ":3"
+ while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34"
+ in command "@echo ${FIB:3"
+ in target "mod-sysv-parse-1"
+make: Unfinished modifier after "", expecting "}"
+ while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34"
+ in command "@echo ${FIB:3="
+ in target "mod-sysv-parse-2"
+make: Unfinished modifier after "x3", expecting "}"
+ while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34"
+ in command "@echo ${FIB:3=x3"
+ in target "mod-sysv-parse-3"
1 1 2 x3 5 8 1x3 21 34
-
-exit status 0
+exit status 2
diff --git a/contrib/bmake/unit-tests/moderrs.mk b/contrib/bmake/unit-tests/moderrs.mk
index ffd920314c5d..c2ee320e2df2 100644
--- a/contrib/bmake/unit-tests/moderrs.mk
+++ b/contrib/bmake/unit-tests/moderrs.mk
@@ -1,8 +1,7 @@
-# $NetBSD: moderrs.mk,v 1.30 2021/06/21 08:28:37 rillig Exp $
+# $NetBSD: moderrs.mk,v 1.47 2025/06/28 22:39:29 rillig Exp $
#
# various modifier error tests
-'= '\''
VAR= TheVariable
# in case we have to change it ;-)
MOD_UNKN= Z
@@ -13,43 +12,45 @@ FIB= 1 1 2 3 5 8 13 21 34
all: mod-unknown-direct mod-unknown-indirect
all: unclosed-direct unclosed-indirect
-all: unfinished-indirect unfinished-loop
-all: loop-close
-all: words
-all: exclam
-all: mod-subst-delimiter
-all: mod-regex-delimiter
-all: mod-ts-parse
-all: mod-t-parse
-all: mod-ifelse-parse
+all: unfinished-indirect unfinished-loop-{1,2,3}
+all: loop-close-{1,2}
+all: words-{1,2,3}
+all: exclam-{1,2}
+all: mod-subst-delimiter-{1,2,3,4,5,6,7}
+all: mod-regex-delimiter-{1,2,3,4,5,6,7}
+all: mod-ts-parse-{1,2,3,4,5}
+all: mod-t-parse-{1,2,3,4}
+all: mod-ifelse-parse-{1,2,3,4,5}
all: mod-remember-parse
-all: mod-sysv-parse
+all: mod-sysv-parse-{1,2,3,4}
-mod-unknown-direct: print-header print-footer
- @echo 'want: Unknown modifier $'Z$''
+mod-unknown-direct:
+# expect: make: Unknown modifier ":Z"
@echo 'VAR:Z=before-${VAR:Z}-after'
-mod-unknown-indirect: print-header print-footer
- @echo 'want: Unknown modifier $'Z$''
+mod-unknown-indirect:
+# expect: make: Unknown modifier ":Z"
@echo 'VAR:${MOD_UNKN}=before-${VAR:${MOD_UNKN}:inner}-after'
-unclosed-direct: print-header print-footer
- @echo 'want: Unclosed variable expression, expecting $'}$' for modifier "S,V,v," of variable "VAR" with value "Thevariable"'
+unclosed-direct:
+# expect: make: Unclosed expression, expecting "}" for modifier "S,V,v,"
@echo VAR:S,V,v,=${VAR:S,V,v,
-unclosed-indirect: print-header print-footer
- @echo 'want: Unclosed variable expression after indirect modifier, expecting $'}$' for variable "VAR"'
+unclosed-indirect:
+# expect: make: Unclosed expression after indirect modifier, expecting "}"
@echo VAR:${MOD_TERM},=${VAR:${MOD_S}
-unfinished-indirect: print-header print-footer
- @echo 'want: Unfinished modifier for VAR ($',$' missing)'
+unfinished-indirect:
+# expect: make: Unfinished modifier after "v", expecting ","
-@echo "VAR:${MOD_TERM}=${VAR:${MOD_TERM}}"
-unfinished-loop: print-header print-footer
- @echo 'want: Unfinished modifier for UNDEF ($'@$' missing)'
+unfinished-loop-1:
+# expect: make: Unfinished modifier after "var}", expecting "@"
@echo ${UNDEF:U1 2 3:@var}
- @echo 'want: Unfinished modifier for UNDEF ($'@$' missing)'
+unfinished-loop-2:
+# expect: make: Unfinished modifier after "...}", expecting "@"
@echo ${UNDEF:U1 2 3:@var@...}
+unfinished-loop-3:
@echo ${UNDEF:U1 2 3:@var@${var}@}
# The closing brace after the ${var} is part of the replacement string.
@@ -58,16 +59,20 @@ unfinished-loop: print-header print-footer
# braces must be balanced.
# This is also contrary to the SysV modifier, where only the actually
# used delimiter (either braces or parentheses) must be balanced.
-loop-close: print-header print-footer
+loop-close-1:
+# expect: make: Unclosed expression, expecting "}" for modifier "@var@${var}}...@"
@echo ${UNDEF:U1 2 3:@var@${var}}...@
+loop-close-2:
@echo ${UNDEF:U1 2 3:@var@${var}}...@}
-words: print-header print-footer
- @echo 'want: Unfinished modifier for UNDEF ($']$' missing)'
+words-1:
+# expect: make: Unfinished modifier after "}", expecting "]"
@echo ${UNDEF:U1 2 3:[}
- @echo 'want: Unfinished modifier for UNDEF ($']$' missing)'
+words-2:
+# expect: make: Unfinished modifier after "#}", expecting "]"
@echo ${UNDEF:U1 2 3:[#}
+words-3:
# out of bounds => empty
@echo 13=${UNDEF:U1 2 3:[13]}
@@ -90,65 +95,114 @@ words: print-header print-footer
# That variable is undefined, resulting in an empty string.
@echo 12345=${UNDEF:U1 2 3:[123451234512345123451234512345]:S,^$,ok,:S,^3$,ok,}
-exclam: print-header print-footer
- @echo 'want: Unfinished modifier for VARNAME ($'!$' missing)'
+exclam-1:
+# expect: make: Unfinished modifier after "echo}", expecting "!"
@echo ${VARNAME:!echo}
# When the final exclamation mark is missing, there is no
# fallback to the SysV substitution modifier.
# If there were a fallback, the output would be "exclam",
# and the above would have produced an "Unknown modifier '!'".
- @echo 'want: Unfinished modifier for ! ($'!$' missing)'
+exclam-2:
+# expect: make: Unfinished modifier after "=exclam}", expecting "!"
@echo ${!:L:!=exclam}
-mod-subst-delimiter: print-header print-footer
+mod-subst-delimiter-1:
+# expect: make: Missing delimiter for modifier ":S"
@echo 1: ${VAR:S
+mod-subst-delimiter-2:
+# expect: make: Unfinished modifier after "", expecting ","
@echo 2: ${VAR:S,
+mod-subst-delimiter-3:
+# expect: make: Unfinished modifier after "from", expecting ","
@echo 3: ${VAR:S,from
+mod-subst-delimiter-4:
+# expect: make: Unfinished modifier after "", expecting ","
@echo 4: ${VAR:S,from,
+mod-subst-delimiter-5:
+# expect: make: Unfinished modifier after "to", expecting ","
@echo 5: ${VAR:S,from,to
+mod-subst-delimiter-6:
+# expect: make: Unclosed expression, expecting "}" for modifier "S,from,to,"
@echo 6: ${VAR:S,from,to,
+mod-subst-delimiter-7:
@echo 7: ${VAR:S,from,to,}
-mod-regex-delimiter: print-header print-footer
+mod-regex-delimiter-1:
+# expect: make: Missing delimiter for modifier ":C"
@echo 1: ${VAR:C
+mod-regex-delimiter-2:
+# expect: make: Unfinished modifier after "", expecting ","
@echo 2: ${VAR:C,
+mod-regex-delimiter-3:
+# expect: make: Unfinished modifier after "from", expecting ","
@echo 3: ${VAR:C,from
+mod-regex-delimiter-4:
+# expect: make: Unfinished modifier after "", expecting ","
@echo 4: ${VAR:C,from,
+mod-regex-delimiter-5:
+# expect: make: Unfinished modifier after "to", expecting ","
@echo 5: ${VAR:C,from,to
+mod-regex-delimiter-6:
+# expect: make: Unclosed expression, expecting "}" for modifier "C,from,to,"
@echo 6: ${VAR:C,from,to,
+mod-regex-delimiter-7:
@echo 7: ${VAR:C,from,to,}
-mod-ts-parse: print-header print-footer
+mod-ts-parse-1:
@echo ${FIB:ts}
+mod-ts-parse-2:
@echo ${FIB:ts\65} # octal 065 == U+0035 == '5'
+mod-ts-parse-3:
+# expect: make: Unknown modifier ":ts\65oct"
@echo ${FIB:ts\65oct} # bad modifier
+mod-ts-parse-4:
+# expect: make: Unknown modifier ":ts\65oct"
@echo ${:U${FIB}:ts\65oct} # bad modifier, variable name is ""
+mod-ts-parse-5:
+# expect: make: Unknown modifier ":tsxy"
@echo ${FIB:tsxy} # modifier too long
-mod-t-parse: print-header print-footer
+mod-t-parse-1:
+# expect: make: Unknown modifier ":t"
@echo ${FIB:t
+mod-t-parse-2:
+# expect: make: Unknown modifier ":txy"
@echo ${FIB:txy}
+mod-t-parse-3:
+# expect: make: Unknown modifier ":t"
@echo ${FIB:t}
+mod-t-parse-4:
+# expect: make: Unknown modifier ":t"
@echo ${FIB:t:M*}
-mod-ifelse-parse: print-header print-footer
+mod-ifelse-parse-1:
+# expect: make: Unfinished modifier after "", expecting ":"
@echo ${FIB:?
+mod-ifelse-parse-2:
+# expect: make: Unfinished modifier after "then", expecting ":"
@echo ${FIB:?then
+mod-ifelse-parse-3:
+# expect: make: Unfinished modifier after "", expecting "}"
@echo ${FIB:?then:
+mod-ifelse-parse-4:
+# expect: make: Unfinished modifier after "else", expecting "}"
@echo ${FIB:?then:else
+mod-ifelse-parse-5:
@echo ${FIB:?then:else}
-mod-remember-parse: print-header print-footer
+mod-remember-parse:
@echo ${FIB:_} # ok
+# expect: make: Unknown modifier ":__"
@echo ${FIB:__} # modifier name too long
-mod-sysv-parse: print-header print-footer
+mod-sysv-parse-1:
+# expect: make: Unknown modifier ":3"
@echo ${FIB:3
+mod-sysv-parse-2:
+# expect: make: Unfinished modifier after "", expecting "}"
@echo ${FIB:3=
+mod-sysv-parse-3:
+# expect: make: Unfinished modifier after "x3", expecting "}"
@echo ${FIB:3=x3
+mod-sysv-parse-4:
@echo ${FIB:3=x3} # ok
-
-print-header: .USEBEFORE
- @echo $@:
-print-footer: .USE
- @echo
diff --git a/contrib/bmake/unit-tests/modmatch.exp b/contrib/bmake/unit-tests/modmatch.exp
deleted file mode 100644
index fcaf6c02ed69..000000000000
--- a/contrib/bmake/unit-tests/modmatch.exp
+++ /dev/null
@@ -1,17 +0,0 @@
-LIB=a X_LIBS:M${LIB${LIB:tu}} is "/tmp/liba.a"
-LIB=a X_LIBS:M*/lib${LIB}.a is "/tmp/liba.a"
-LIB=a X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBA.A"
-LIB=b X_LIBS:M${LIB${LIB:tu}} is ""
-LIB=b X_LIBS:M*/lib${LIB}.a is ""
-LIB=b X_LIBS:M*/lib${LIB}.a:tu is ""
-LIB=c X_LIBS:M${LIB${LIB:tu}} is ""
-LIB=c X_LIBS:M*/lib${LIB}.a is ""
-LIB=c X_LIBS:M*/lib${LIB}.a:tu is ""
-LIB=d X_LIBS:M${LIB${LIB:tu}} is "/tmp/libd.a"
-LIB=d X_LIBS:M*/lib${LIB}.a is "/tmp/libd.a"
-LIB=d X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBD.A"
-LIB=e X_LIBS:M${LIB${LIB:tu}} is "/tmp/libe.a"
-LIB=e X_LIBS:M*/lib${LIB}.a is "/tmp/libe.a"
-LIB=e X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBE.A"
-Mscanner=OK
-exit status 0
diff --git a/contrib/bmake/unit-tests/modmatch.mk b/contrib/bmake/unit-tests/modmatch.mk
deleted file mode 100644
index 7dcacf09da6d..000000000000
--- a/contrib/bmake/unit-tests/modmatch.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-# $NetBSD: modmatch.mk,v 1.9 2020/10/24 08:50:17 rillig Exp $
-#
-# Tests for the :M and :S modifiers.
-
-X= a b c d e
-
-.for x in $X
-LIB${x:tu}= /tmp/lib$x.a
-.endfor
-
-X_LIBS= ${LIBA} ${LIBD} ${LIBE}
-
-LIB?= a
-
-var= head
-res= no
-.if !empty(var:M${:Uhead\:tail:C/:.*//})
-res= OK
-.endif
-
-all: show-libs
-
-show-libs:
- @for x in $X; do ${.MAKE} -f ${MAKEFILE} show LIB=$$x; done
- @echo "Mscanner=${res}"
-
-show:
- @echo 'LIB=${LIB} X_LIBS:M$${LIB$${LIB:tu}} is "${X_LIBS:M${LIB${LIB:tu}}}"'
- @echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a is "${X_LIBS:M*/lib${LIB}.a}"'
- @echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a:tu is "${X_LIBS:M*/lib${LIB}.a:tu}"'
diff --git a/contrib/bmake/unit-tests/modmisc.exp b/contrib/bmake/unit-tests/modmisc.exp
index 10475e65ee0f..f243511ab491 100644
--- a/contrib/bmake/unit-tests/modmisc.exp
+++ b/contrib/bmake/unit-tests/modmisc.exp
@@ -6,7 +6,6 @@ path='/bin':'/tmp':'/':'/no/such/dir'
path_/usr/xbin=/opt/xbin/
paths=/bin /tmp / /no/such/dir /opt/xbin
PATHS=/BIN /TMP / /NO/SUCH/DIR /OPT/XBIN
-The answer is 42
S:
C:
@:
diff --git a/contrib/bmake/unit-tests/modmisc.mk b/contrib/bmake/unit-tests/modmisc.mk
index 9ace35c15162..4868abef92f1 100644
--- a/contrib/bmake/unit-tests/modmisc.mk
+++ b/contrib/bmake/unit-tests/modmisc.mk
@@ -1,4 +1,4 @@
-# $NetBSD: modmisc.mk,v 1.52 2020/12/20 19:29:06 rillig Exp $
+# $NetBSD: modmisc.mk,v 1.53 2023/06/16 07:20:45 rillig Exp $
#
# miscellaneous modifier tests
@@ -15,14 +15,10 @@ MOD_HOMES= S,/home/,/homes/,
MOD_OPT= @d@$${exists($$d):?$$d:$${d:S,/usr,/opt,}}@
MOD_SEP= S,:, ,g
-all: modvar modvarloop modsysv emptyvar undefvar
+all: modvar modvarloop emptyvar undefvar
all: mod-quote
all: mod-break-many-words
-# See also sysv.mk.
-modsysv:
- @echo "The answer is ${libfoo.a:L:libfoo.a=42}"
-
# Demonstrates modifiers that are given indirectly from a variable.
modvar:
@echo "path='${path}'"
@@ -60,6 +56,6 @@ undefvar:
mod-quote:
@echo $@: new${.newline:Q}${.newline:Q}line
-# Cover the bmake_realloc in Str_Words.
+# Cover the bmake_realloc in Substring_Words.
mod-break-many-words:
@echo $@: ${UNDEF:U:range=500:[#]}
diff --git a/contrib/bmake/unit-tests/objdir-writable.exp b/contrib/bmake/unit-tests/objdir-writable.exp
index dc5cd706349e..3fbf82c0522a 100644
--- a/contrib/bmake/unit-tests/objdir-writable.exp
+++ b/contrib/bmake/unit-tests/objdir-writable.exp
@@ -1,4 +1,4 @@
-make: warning: <tmpdir>/roobj: Permission denied.
+make: warning: <tmpdir>/roobj: Permission denied
<tmpdir>
<tmpdir>/roobj
<tmpdir>/roobj
diff --git a/contrib/bmake/unit-tests/opt-chdir.exp b/contrib/bmake/unit-tests/opt-chdir.exp
index d9759cf9ed8b..3d89360f9a62 100644
--- a/contrib/bmake/unit-tests/opt-chdir.exp
+++ b/contrib/bmake/unit-tests/opt-chdir.exp
@@ -1,5 +1,3 @@
-make: chdir /./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././: File name too long
-*** Error code 2 (ignored)
cwd: /
make: chdir /nonexistent: No such file or directory
*** Error code 2 (ignored)
diff --git a/contrib/bmake/unit-tests/opt-chdir.mk b/contrib/bmake/unit-tests/opt-chdir.mk
index a8806149f31c..e94b8799af2e 100644
--- a/contrib/bmake/unit-tests/opt-chdir.mk
+++ b/contrib/bmake/unit-tests/opt-chdir.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-chdir.mk,v 1.6 2021/05/18 17:05:45 sjg Exp $
+# $NetBSD: opt-chdir.mk,v 1.7 2024/04/02 11:11:00 rillig Exp $
#
# Tests for the -C command line option, which changes the directory at the
# beginning.
@@ -7,15 +7,9 @@
.MAKEFLAGS: -d0 # switch stdout to line-buffered
-all: chdir-filename-too-long
all: chdir-root
all: chdir-nonexistent
-# Try to overflow the internal buffer for .CURDIR, which is curdir.
-chdir-filename-too-long: .PHONY .IGNORE
- # 5000 slashes, separated by dots: /./././.../././
- @${MAKE} -C ${:U:range=5000:@@/@:ts.}
-
# Changing to another directory is possible via the command line.
# In this test, it is the root directory since almost any other directory
# is not guaranteed to exist on every platform.
diff --git a/contrib/bmake/unit-tests/opt-debug-errors-jobs.exp b/contrib/bmake/unit-tests/opt-debug-errors-jobs.exp
index c957c7736b32..a2ad864ba4e6 100644
--- a/contrib/bmake/unit-tests/opt-debug-errors-jobs.exp
+++ b/contrib/bmake/unit-tests/opt-debug-errors-jobs.exp
@@ -2,57 +2,63 @@ echo '3 spaces'; false
3 spaces
*** Failed target: fail-spaces
+*** In directory: <curdir>
*** Failed commands:
echo '3 spaces'; false
*** [fail-spaces] Error code 1
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
echo \ indented; false
indented
*** Failed target: fail-escaped-space
+*** In directory: <curdir>
*** Failed commands:
echo \ indented; false
*** [fail-escaped-space] Error code 1
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
echo 'line1
line2'; false
line1
line2
*** Failed target: fail-newline
+*** In directory: <curdir>
*** Failed commands:
echo 'line1${.newline}line2'; false
=> echo 'line1
line2'; false
*** [fail-newline] Error code 1
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
echo 'line1 line2'; false
line1 line2
*** Failed target: fail-multiline
+*** In directory: <curdir>
*** Failed commands:
echo 'line1 line2'; false
*** [fail-multiline] Error code 1
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
echo 'word1' 'word2'; false
word1 word2
*** Failed target: fail-multiline-intention
+*** In directory: <curdir>
*** Failed commands:
echo 'word1' 'word2'; false
*** [fail-multiline-intention] Error code 1
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
*** Failed target: fail-vars
+*** In directory: <curdir>
*** Failed commands:
@${COMPILE_C} ${COMPILE_C_FLAGS}
=> @false c-compiler flag1 -macro="several words"
*** [fail-vars] Error code 1
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/opt-debug-errors.exp b/contrib/bmake/unit-tests/opt-debug-errors.exp
index 859a431f23bb..b0685a3c7126 100644
--- a/contrib/bmake/unit-tests/opt-debug-errors.exp
+++ b/contrib/bmake/unit-tests/opt-debug-errors.exp
@@ -33,5 +33,5 @@ word1 word2
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/opt-debug-file.exp b/contrib/bmake/unit-tests/opt-debug-file.exp
index 712686f60b3c..4a497f3011d9 100644
--- a/contrib/bmake/unit-tests/opt-debug-file.exp
+++ b/contrib/bmake/unit-tests/opt-debug-file.exp
@@ -1,12 +1,12 @@
-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.
+make: opt-debug-file.mk:54: This goes to stderr only, once.
+make: opt-debug-file.mk:57: This goes to stderr only, once.
+make: opt-debug-file.mk:60: This goes to stderr, and in addition to the debug log.
CondParser_Eval: ${:!cat opt-debug-file.debuglog!:Maddition:[#]} != 1
Comparing 1.000000 != 1.000000
-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
+make: Unterminated quoted string [make 'This goes to stdout only, once.]
+make: Unterminated quoted string [make 'This goes to stderr only, once.]
+make: Unterminated quoted string [make 'This goes to stderr, and in addition to the debug log.]
+CondParser_Eval: ${:!cat opt-debug-file.debuglog!:MUnterminated:[#]} != 1
Comparing 1.000000 != 1.000000
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 b878c2bcf734..d107f177dae3 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.8 2022/01/11 19:47:34 rillig Exp $
+# $NetBSD: opt-debug-file.mk,v 1.12 2025/07/06 08:48:34 rillig Exp $
#
# Tests for the -dF command line option, which redirects the debug log
# to a file instead of writing it to stderr.
@@ -18,7 +18,7 @@ VAR= value ${:Uexpanded}
# 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
+# value contains ${:Uexpanded}. This expression is expanded
# when it is used in the condition below. Therefore, be careful when storing
# untrusted input in variables.
#.MAKEFLAGS: -dc -dFstderr
@@ -27,7 +27,9 @@ DEBUG_OUTPUT:= ${:!cat opt-debug-file.debuglog!}
.endif
# 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.
+# file, the content of that log file must not be stored in a variable
+# directly. Instead, it can be processed in a single expression by a chain
+# of modifiers.
#
# XXX: In the :M modifier, a dollar is escaped using '$$', not '\$'. This
# escaping scheme unnecessarily differs from all other modifiers.
@@ -35,15 +37,26 @@ DEBUG_OUTPUT:= ${:!cat opt-debug-file.debuglog!}
. error
.endif
+# 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
+# directly. Instead, each dollar sign must be escaped first.
+DEBUG_OUTPUT:= ${:!cat opt-debug-file.debuglog!:S,\$,\$\$,g}
+.if ${DEBUG_OUTPUT:M*Uexpanded*} != "\${:Uexpanded}"
+. error
+.endif
+
.MAKEFLAGS: -d0
# See Parse_Error.
.MAKEFLAGS: -dFstdout
+# expect+1: This goes to stderr only, once.
. info This goes to stderr only, once.
.MAKEFLAGS: -dFstderr
+# expect+1: This goes to stderr only, once.
. info This goes to stderr only, once.
.MAKEFLAGS: -dFopt-debug-file.debuglog
+# expect+1: This goes to stderr, and in addition to the debug log.
. info This goes to stderr, and in addition to the debug log.
.MAKEFLAGS: -dFstderr -d0c
.if ${:!cat opt-debug-file.debuglog!:Maddition:[#]} != 1
@@ -51,15 +64,18 @@ DEBUG_OUTPUT:= ${:!cat opt-debug-file.debuglog!}
.endif
-# See ApplyModifier_Subst, which calls Error.
+# See Main_ParseArgLine, which calls Error.
.MAKEFLAGS: -dFstdout
-: This goes to stderr only, once. ${:U:S
+# expect: make: Unterminated quoted string [make 'This goes to stdout only, once.]
+.MAKEFLAGS: 'This goes to stdout only, once.
.MAKEFLAGS: -dFstderr
-: This goes to stderr only, once. ${:U:S
+# expect: make: Unterminated quoted string [make 'This goes to stderr only, once.]
+.MAKEFLAGS: 'This goes to stderr only, once.
.MAKEFLAGS: -dFopt-debug-file.debuglog
-: This goes to stderr, and in addition to the debug log. ${:U:S
+# expect: make: Unterminated quoted string [make 'This goes to stderr, and in addition to the debug log.]
+.MAKEFLAGS: 'This goes to stderr, and in addition to the debug log.
.MAKEFLAGS: -dFstderr -d0c
-.if ${:!cat opt-debug-file.debuglog!:Mdelimiter:[#]} != 1
+.if ${:!cat opt-debug-file.debuglog!:MUnterminated:[#]} != 1
. error
.endif
diff --git a/contrib/bmake/unit-tests/opt-debug-for.exp b/contrib/bmake/unit-tests/opt-debug-for.exp
index ea811b9bfcf5..a8f63b85fec3 100644
--- a/contrib/bmake/unit-tests/opt-debug-for.exp
+++ b/contrib/bmake/unit-tests/opt-debug-for.exp
@@ -1,22 +1,22 @@
For: new loop 2
For: end for 2
For: end for 1
-For: loop body:
+For: loop body with outer = a:
. for inner in 1 2
VAR.${:Ua}${inner}= value
. endfor
For: end for 1
-For: loop body:
+For: loop body with inner = 1:
VAR.${:Ua}${:U1}= value
-For: loop body:
+For: loop body with inner = 2:
VAR.${:Ua}${:U2}= value
-For: loop body:
+For: loop body with outer = b:
. for inner in 1 2
VAR.${:Ub}${inner}= value
. endfor
For: end for 1
-For: loop body:
+For: loop body with inner = 1:
VAR.${:Ub}${:U1}= value
-For: loop body:
+For: loop body with inner = 2:
VAR.${:Ub}${:U2}= value
exit status 0
diff --git a/contrib/bmake/unit-tests/opt-debug-graph1.exp b/contrib/bmake/unit-tests/opt-debug-graph1.exp
index 64dcece5f026..9dae95302318 100644
--- a/contrib/bmake/unit-tests/opt-debug-graph1.exp
+++ b/contrib/bmake/unit-tests/opt-debug-graph1.exp
@@ -1,4 +1,4 @@
-#*** Input graph:
+#*** Begin input graph for pass 1 in <curdir>:
# all, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none
# made-target, unmade, type OP_DEPENDS, flags none
# made-target-no-sources, unmade, type OP_DEPENDS, flags none
@@ -21,7 +21,9 @@
.MAKE = <details omitted>
.MAKE.DEPENDFILE = <details omitted>
.MAKE.GID = <details omitted>
+.MAKE.JOBS.C = <details omitted>
.MAKE.LEVEL = <details omitted>
+.MAKE.LEVEL.ENV = MAKELEVEL
.MAKE.MAKEFILES = <details omitted>
.MAKE.MAKEFILE_PREFERENCE = <details omitted>
.MAKE.OS = <details omitted>
@@ -40,7 +42,6 @@ MACHINE_ARCH = <details omitted>
MAKE = <details omitted>
MFLAGS = -r -k -d g1
#*** Command-line Variables:
-.MAKE.LEVEL.ENV = MAKELEVEL
#*** Directory Cache:
# Stats: 0 hits 2 misses 0 near misses 0 losers (0%)
@@ -49,4 +50,5 @@ MFLAGS = -r -k -d g1
#*** Suffixes:
#*** Transformations:
+#*** End input graph for pass 1 in <curdir>:
exit status 0
diff --git a/contrib/bmake/unit-tests/opt-debug-graph2.exp b/contrib/bmake/unit-tests/opt-debug-graph2.exp
index 89e10b181c2c..e4160e413787 100644
--- a/contrib/bmake/unit-tests/opt-debug-graph2.exp
+++ b/contrib/bmake/unit-tests/opt-debug-graph2.exp
@@ -4,7 +4,7 @@ false
false
*** Error code 1 (continuing)
`all' not remade because of errors.
-#*** Input graph:
+#*** Begin input graph for pass 2 in <curdir>:
# made-target, made, type OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS|OP_DEPS_FOUND|OP_MARK, flags REMAKE|DONE_ALLSRC
#
# *** MAIN TARGET ***
@@ -55,7 +55,9 @@ all : made-target error-target aborted-target
.MAKE = <details omitted>
.MAKE.DEPENDFILE = <details omitted>
.MAKE.GID = <details omitted>
+.MAKE.JOBS.C = <details omitted>
.MAKE.LEVEL = <details omitted>
+.MAKE.LEVEL.ENV = MAKELEVEL
.MAKE.MAKEFILES = <details omitted>
.MAKE.MAKEFILE_PREFERENCE = <details omitted>
.MAKE.OS = <details omitted>
@@ -74,7 +76,6 @@ MACHINE_ARCH = <details omitted>
MAKE = <details omitted>
MFLAGS = -r -k -d g2
#*** Command-line Variables:
-.MAKE.LEVEL.ENV = MAKELEVEL
.SHELL = <details omitted>
#*** Directory Cache:
@@ -84,7 +85,8 @@ MFLAGS = -r -k -d g2
#*** Suffixes:
#*** Transformations:
+#*** End input graph for pass 2 in <curdir>:
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/opt-debug-graph3.exp b/contrib/bmake/unit-tests/opt-debug-graph3.exp
index 36706145eb14..ccebfd7b16bc 100644
--- a/contrib/bmake/unit-tests/opt-debug-graph3.exp
+++ b/contrib/bmake/unit-tests/opt-debug-graph3.exp
@@ -4,7 +4,7 @@ false
false
*** Error code 1 (continuing)
`all' not remade because of errors.
-#*** Input graph:
+#*** Begin input graph for pass 3 in <curdir>:
# made-target, made, type OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS|OP_DEPS_FOUND|OP_MARK, flags REMAKE|DONE_ALLSRC
#
# *** MAIN TARGET ***
@@ -55,7 +55,9 @@ all : made-target error-target aborted-target
.MAKE = <details omitted>
.MAKE.DEPENDFILE = <details omitted>
.MAKE.GID = <details omitted>
+.MAKE.JOBS.C = <details omitted>
.MAKE.LEVEL = <details omitted>
+.MAKE.LEVEL.ENV = MAKELEVEL
.MAKE.MAKEFILES = <details omitted>
.MAKE.MAKEFILE_PREFERENCE = <details omitted>
.MAKE.OS = <details omitted>
@@ -74,7 +76,6 @@ MACHINE_ARCH = <details omitted>
MAKE = <details omitted>
MFLAGS = -r -k -d g3
#*** Command-line Variables:
-.MAKE.LEVEL.ENV = MAKELEVEL
.SHELL = <details omitted>
#*** Directory Cache:
@@ -84,7 +85,8 @@ MFLAGS = -r -k -d g3
#*** Suffixes:
#*** Transformations:
+#*** End input graph for pass 3 in <curdir>:
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/opt-debug-hash.exp b/contrib/bmake/unit-tests/opt-debug-hash.exp
index b239399ec44d..1976b142b06f 100644
--- a/contrib/bmake/unit-tests/opt-debug-hash.exp
+++ b/contrib/bmake/unit-tests/opt-debug-hash.exp
@@ -1,6 +1,6 @@
-make: "opt-debug-hash.mk" line 11: Missing argument for ".error"
+make: opt-debug-hash.mk:13: 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
+HashTable "targets": size=16 entries=0 maxchain=0
+HashTable "Global variables": size=16 entries=<entries> maxchain=4
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 8b757ff3f290..88edbedd6fd9 100644
--- a/contrib/bmake/unit-tests/opt-debug-hash.mk
+++ b/contrib/bmake/unit-tests/opt-debug-hash.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-debug-hash.mk,v 1.3 2022/01/22 18:59:24 rillig Exp $
+# $NetBSD: opt-debug-hash.mk,v 1.5 2024/05/31 07:13:12 rillig Exp $
#
# Tests for the -dh command line option, which adds debug logging for
# hash tables. Even more detailed logging is available by compiling
@@ -6,6 +6,8 @@
.MAKEFLAGS: -dh
-# 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.
+# Force a parse error, to demonstrate the newline character in the "cannot
+# continue" diagnostic that had been missing before parse.c 1.655 from
+# 2022-01-22.
+# expect+1: Missing argument for ".error"
.error
diff --git a/contrib/bmake/unit-tests/opt-debug-jobs.exp b/contrib/bmake/unit-tests/opt-debug-jobs.exp
index e79d8e94a952..6cda45107702 100644
--- a/contrib/bmake/unit-tests/opt-debug-jobs.exp
+++ b/contrib/bmake/unit-tests/opt-debug-jobs.exp
@@ -1,6 +1,6 @@
job_pipe -1 -1, maxjobs 1, tokens 1, compat 0
-Job_TokenWithdraw(<pid>): aborting 0, running 0
-(<pid>) withdrew token
+TokenPool_Take: pid <pid>, aborting NONE, running 0
+TokenPool_Take: pid <pid> took a token
echo ": expanded expression"
{ : expanded expression
} || exit $?
@@ -13,15 +13,15 @@ echo ": 'single' and \"double\" quotes"
{ sleep 1
} || exit $?
Running all
- Command: <shell>
-JobExec(all): pid <pid> added to jobs table
-job table @ job started
-job 0, status 3, flags ---, pid <pid>
+ Command: <shell>
+JobExec: target all, pid <pid> added to jobs table
+job started, job table:
+job 0, status running, flags ---, pid <pid>
: expanded expression
: variable
: 'single' and "double" quotes
-Process <pid> exited/stopped status 0.
-JobFinish: <pid> [all], status 0
-Job_TokenWithdraw(<pid>): aborting 0, running 0
-(<pid>) withdrew token
+Process with pid <pid> exited/stopped with status 0.
+JobFinish: target all, pid <pid>, status 0
+TokenPool_Take: pid <pid>, aborting NONE, running 0
+TokenPool_Take: pid <pid> took a token
exit status 0
diff --git a/contrib/bmake/unit-tests/opt-debug-jobs.mk b/contrib/bmake/unit-tests/opt-debug-jobs.mk
index f3732df7e25d..ac63bb9c5e86 100644
--- a/contrib/bmake/unit-tests/opt-debug-jobs.mk
+++ b/contrib/bmake/unit-tests/opt-debug-jobs.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-debug-jobs.mk,v 1.5 2020/11/12 21:54:52 rillig Exp $
+# $NetBSD: opt-debug-jobs.mk,v 1.6 2023/11/19 21:47:52 rillig Exp $
#
# Tests for the -dj command line option, which adds debug logging about
# running jobs in multiple shells.
@@ -11,7 +11,7 @@
all:
# Only the actual command is logged.
- # To see the evaluation of the variable expressions, use -dv.
+ # To see the evaluation of the expressions, use -dv.
: ${:Uexpanded} expression
# Undefined variables expand to empty strings.
diff --git a/contrib/bmake/unit-tests/opt-debug-lint.exp b/contrib/bmake/unit-tests/opt-debug-lint.exp
index 05b341b30dae..7173ec476ec5 100644
--- a/contrib/bmake/unit-tests/opt-debug-lint.exp
+++ b/contrib/bmake/unit-tests/opt-debug-lint.exp
@@ -1,8 +1,11 @@
-make: "opt-debug-lint.mk" line 19: Variable "X" is undefined
-make: "opt-debug-lint.mk" line 41: Variable "UNDEF" is undefined
-make: "opt-debug-lint.mk" line 61: Missing delimiter ':' after modifier "L"
-make: "opt-debug-lint.mk" line 61: Missing delimiter ':' after modifier "P"
-make: "opt-debug-lint.mk" line 69: Unknown modifier "${"
+make: opt-debug-lint.mk:20: Variable "X" is undefined
+make: opt-debug-lint.mk:43: Variable "UNDEF" is undefined
+make: opt-debug-lint.mk:65: Missing delimiter ":" after modifier "L"
+ while evaluating variable "value" with value "value"
+make: opt-debug-lint.mk:65: Missing delimiter ":" after modifier "P"
+ while evaluating variable "value" with value "value"
+make: opt-debug-lint.mk:74: Unknown modifier ":${"
+ while evaluating variable "value" with value ""
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/opt-debug-lint.mk b/contrib/bmake/unit-tests/opt-debug-lint.mk
index 155e1a3de3be..2f73c9bf645c 100644
--- a/contrib/bmake/unit-tests/opt-debug-lint.mk
+++ b/contrib/bmake/unit-tests/opt-debug-lint.mk
@@ -1,7 +1,7 @@
-# $NetBSD: opt-debug-lint.mk,v 1.14 2021/03/14 10:57:12 rillig Exp $
+# $NetBSD: opt-debug-lint.mk,v 1.25 2025/06/28 22:39:29 rillig Exp $
#
# Tests for the -dL command line option, which runs additional checks
-# to catch common mistakes, such as unclosed variable expressions.
+# to catch common mistakes, such as unclosed expressions.
.MAKEFLAGS: -dL
@@ -16,6 +16,7 @@
#
# See also:
# cond-undef-lint.mk
+# expect+1: Variable "X" is undefined
.if $X
. error
.endif
@@ -38,6 +39,7 @@
# hoping for the caller to print an error message. This resulted in the
# well-known "Malformed conditional" error message, even though the
# conditional was well-formed and the only error was an undefined variable.
+# expect+1: Variable "UNDEF" is undefined
.if ${UNDEF}
. error
.endif
@@ -58,6 +60,8 @@ ${UNDEF}: ${UNDEF}
# Since 2020-10-03, in lint mode the variable modifier must be separated
# by colons. See varparse-mod.mk.
+# expect+2: Missing delimiter ":" after modifier "L"
+# expect+1: Missing delimiter ":" after modifier "P"
.if ${value:LPL} != "value"
. error
.endif
@@ -66,8 +70,11 @@ ${UNDEF}: ${UNDEF}
# variable modifier had to be separated by colons. This was wrong though
# since make always fell back trying to parse the indirect modifier as a
# SysV modifier.
-.if ${value:${:UL}PL} != "LPL}" # FIXME: "LPL}" is unexpected here.
+# expect+1: Unknown modifier ":${"
+.if ${value:${:UL}PL} != ""
. error ${value:${:UL}PL}
+.else
+. error
.endif
# Typically, an indirect modifier is followed by a colon or the closing
@@ -84,7 +91,7 @@ ${UNDEF}: ${UNDEF}
#
# Before var.c 1.856 from 2021-03-14, this regular expression was then
# compiled even though that was not necessary for checking the syntax at the
-# level of variable expressions. The unexpanded '$' then resulted in a wrong
+# level of expressions. The unexpanded '$' then resulted in a wrong
# error message.
#
# This only happened in lint mode since in default mode the early check for
diff --git a/contrib/bmake/unit-tests/opt-debug-loud.mk b/contrib/bmake/unit-tests/opt-debug-loud.mk
index 38a3c7d7a8e1..5ea1f90ff7be 100644
--- a/contrib/bmake/unit-tests/opt-debug-loud.mk
+++ b/contrib/bmake/unit-tests/opt-debug-loud.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-debug-loud.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $
+# $NetBSD: opt-debug-loud.mk,v 1.5 2023/12/19 19:33:40 rillig Exp $
#
# Tests for the -dl command line option, which prints the commands before
# running them, ignoring the command line option for silent mode (-s) as
@@ -8,8 +8,8 @@
.MAKEFLAGS: -dl -s
.SILENT:
-# The -dl command line option does not affect commands that are run during
-# variable expansion, such as :!cmd! or :sh.
+# The -dl command line option does not affect commands that are run when
+# evaluating expressions and their modifiers, such as :!cmd! or :sh.
.if ${:!echo word!} != "word"
. error
.endif
diff --git a/contrib/bmake/unit-tests/opt-debug-parse.exp b/contrib/bmake/unit-tests/opt-debug-parse.exp
index 0e11024647a1..05659e28ee11 100644
--- a/contrib/bmake/unit-tests/opt-debug-parse.exp
+++ b/contrib/bmake/unit-tests/opt-debug-parse.exp
@@ -1,26 +1,28 @@
-Parse_PushInput: .for loop in opt-debug-parse.mk, line 16
+Parsing opt-debug-parse.mk:16: .for var in value
+Parse_PushInput: .for loop in opt-debug-parse.mk: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
+Parsing opt-debug-parse.mk:21: .info trace with multi-line .for loop head
+make: opt-debug-parse.mk:21: 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
+ParseEOF: returning to opt-debug-parse.mk:23
SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `opt-debug-parse.mk'
-Parsing line 25: .include "/dev/null"
-Parse_PushInput: file /dev/null, line 1
+Parsing opt-debug-parse.mk:26: .include "/dev/null"
+Parse_PushInput: /dev/null: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
+ParseEOF: returning to opt-debug-parse.mk:27
SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `opt-debug-parse.mk'
-Parse_PushInput: .for loop in opt-debug-parse.mk, line 30
+Parsing opt-debug-parse.mk:31: .for a b c in 1 2 3 ${:U4 5 6}
+Parse_PushInput: .for loop in opt-debug-parse.mk:31
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
+Parsing opt-debug-parse.mk:34: .info trace
+make: opt-debug-parse.mk:34: trace
+ in .for loop from opt-debug-parse.mk:31 with a = 1, b = 2, c = 3
+Parsing opt-debug-parse.mk:34: .info trace
+make: opt-debug-parse.mk:34: trace
+ in .for loop from opt-debug-parse.mk:31 with a = 4, b = 5, c = 6
+ParseEOF: returning to opt-debug-parse.mk:36
SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `opt-debug-parse.mk'
-Parsing line 35: .MAKEFLAGS: -d0
+Parsing opt-debug-parse.mk:38: .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 9517bb62b976..347537015b52 100644
--- a/contrib/bmake/unit-tests/opt-debug-parse.mk
+++ b/contrib/bmake/unit-tests/opt-debug-parse.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-debug-parse.mk,v 1.7 2022/02/09 21:09:24 rillig Exp $
+# $NetBSD: opt-debug-parse.mk,v 1.8 2023/06/01 20:56:35 rillig Exp $
#
# Tests for the -dp command line option, which adds debug logging about
# makefile parsing.
@@ -17,6 +17,7 @@
var \
in \
value
+# expect+1: trace with multi-line .for loop head
.info trace with multi-line .for loop head
.endfor
@@ -28,6 +29,8 @@
# 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}
+# expect+2: trace
+# expect+1: trace
.info trace
.endfor
diff --git a/contrib/bmake/unit-tests/opt-debug-var.exp b/contrib/bmake/unit-tests/opt-debug-var.exp
index 5e9d10c671f1..5ee77d2f249d 100644
--- a/contrib/bmake/unit-tests/opt-debug-var.exp
+++ b/contrib/bmake/unit-tests/opt-debug-var.exp
@@ -2,6 +2,12 @@ Global: ASSIGNED = value
Global: SUBST = # (empty)
Global: SUBST = value
Var_Parse: y(ASSIGNED) (eval)
+Var_Parse: $U (eval-defined-loud)
+make: opt-debug-var.mk:34: Variable "U" is undefined
+Var_Parse: $< (eval-defined-loud)
+make: opt-debug-var.mk:40: Variable "<" is undefined
Global: .MAKEFLAGS = -r -k -d v -d
Global: .MAKEFLAGS = -r -k -d v -d 0
-exit status 0
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/opt-debug-var.mk b/contrib/bmake/unit-tests/opt-debug-var.mk
index 5b0c5648ab55..7a3a80f11432 100644
--- a/contrib/bmake/unit-tests/opt-debug-var.mk
+++ b/contrib/bmake/unit-tests/opt-debug-var.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-debug-var.mk,v 1.2 2022/01/23 16:09:38 rillig Exp $
+# $NetBSD: opt-debug-var.mk,v 1.5 2025/01/11 21:21:33 rillig Exp $
#
# Tests for the -dv command line option, which adds debug logging about
# variable assignment and evaluation.
@@ -9,13 +9,14 @@
ASSIGNED= value
# TODO: Explain why the empty assignment "Global: SUBST = " is needed.
+# expect: Global: SUBST = # (empty)
# expect: Global: SUBST = value
SUBST:= value
.if defined(ASSIGNED)
.endif
-# The usual form of variable expressions is ${VAR}. The form $(VAR) is used
+# The usual form of 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.
#
@@ -26,6 +27,18 @@ SUBST:= value
.if !empty(ASSIGNED)
.endif
-.MAKEFLAGS: -d0
-all: .PHONY
+# An expression for a variable with a single-character ordinary name.
+# expect: Var_Parse: $U (eval-defined-loud)
+# expect+1: Variable "U" is undefined
+.if $U
+.endif
+
+# An expression for a target-specific variable with a single-character name.
+# expect: Var_Parse: $< (eval-defined-loud)
+# expect+1: Variable "<" is undefined
+.if $<
+.endif
+
+
+.MAKEFLAGS: -d0
diff --git a/contrib/bmake/unit-tests/opt-define.mk b/contrib/bmake/unit-tests/opt-define.mk
index 7c4bbc179316..f508a9b1592f 100644
--- a/contrib/bmake/unit-tests/opt-define.mk
+++ b/contrib/bmake/unit-tests/opt-define.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-define.mk,v 1.3 2022/01/23 16:09:38 rillig Exp $
+# $NetBSD: opt-define.mk,v 1.4 2022/06/12 14:27:06 rillig Exp $
#
# Tests for the -D command line option, which defines global variables to the
# value 1, like in the C preprocessor.
@@ -19,10 +19,22 @@ VAR= overwritten
.endif
# The variable can be undefined. If the variable had been defined in the
-# "Internal" scope instead, undefining it would have no effect.
+# "Internal" or in the "Command" scope instead, undefining it would have no
+# effect.
.undef VAR
.if defined(VAR)
. error
.endif
+# The C preprocessor allows to define a macro with a specific value. Make
+# behaves differently, it defines a variable with the name 'VAR=value' and the
+# value 1.
+.MAKEFLAGS: -DVAR=value
+.if defined(VAR)
+. error
+.endif
+.if ${VAR=value} != "1"
+. error
+.endif
+
all: .PHONY
diff --git a/contrib/bmake/unit-tests/opt-env.exp b/contrib/bmake/unit-tests/opt-env.exp
index b2e9ea85bafd..39a9383953dd 100644
--- a/contrib/bmake/unit-tests/opt-env.exp
+++ b/contrib/bmake/unit-tests/opt-env.exp
@@ -1,5 +1 @@
-make: "opt-env.mk" line 13: Malformed conditional (${FROM_ENV} != value-from-env)
-make: "opt-env.mk" line 20: value-from-mk
-
-make: stopped in unit-tests
-exit status 1
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-file.exp b/contrib/bmake/unit-tests/opt-file.exp
index 76a832949aca..d915f9fe0170 100644
--- a/contrib/bmake/unit-tests/opt-file.exp
+++ b/contrib/bmake/unit-tests/opt-file.exp
@@ -1,12 +1,11 @@
value
value
line-with-trailing-whitespace
-make: "(stdin)" line 1: Zero byte read from file
-make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
-*** Error code 1 (continuing)
+make: (stdin):1: Zero byte read from file
+ in make[1] in directory "<curdir>"
+*** Error code 2 (continuing)
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/opt-file.mk b/contrib/bmake/unit-tests/opt-file.mk
index 5085fe126af8..0f07ede560f5 100644
--- a/contrib/bmake/unit-tests/opt-file.mk
+++ b/contrib/bmake/unit-tests/opt-file.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-file.mk,v 1.15 2022/03/26 13:32:31 rillig Exp $
+# $NetBSD: opt-file.mk,v 1.16 2024/04/01 12:26:02 rillig Exp $
#
# Tests for the -f command line option, which adds a makefile to the list of
# files that are parsed.
@@ -79,7 +79,7 @@ line-with-trailing-whitespace: .PHONY
# exit status 0
#
# 2008 to 2010:
-# make: "zero-byte.in" line 1: Zero byte read from file
+# make: "(stdin)" line 1: Zero byte read from file
# make: Fatal errors encountered -- cannot continue
#
# make: stopped in .
@@ -92,14 +92,18 @@ line-with-trailing-whitespace: .PHONY
# exit status 2
#
# 2014 to 2020-12-06:
-# make: "zero-byte.in" line 1: warning: Zero byte read from file, skipping rest of line.
+# make: "(stdin)" line 1: warning: Zero byte read from file, skipping rest of line.
# exit status 0
#
# Since 2020-12-07:
-# make: "zero-byte.in" line 1: Zero byte read from file
+# make: "(stdin)" line 1: Zero byte read from file
# make: Fatal errors encountered -- cannot continue
# make: stopped in .
# exit status 1
+#
+# Since 2024-04-01:
+# make: "(stdin)" line 1: Zero byte read from file
+# *** Error code 2 (continuing)
file-containing-null-byte: .PHONY
@printf '%s\n' 'VAR=value' 'VAR2=VALUE2' \
| tr 'l' '\0' \
diff --git a/contrib/bmake/unit-tests/opt-jobs-internal.exp b/contrib/bmake/unit-tests/opt-jobs-internal.exp
index 470bdbddd0f8..61c96256a2e4 100644
--- a/contrib/bmake/unit-tests/opt-jobs-internal.exp
+++ b/contrib/bmake/unit-tests/opt-jobs-internal.exp
@@ -1,6 +1,15 @@
-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
+direct: mode=parallel
+make: error: invalid internal option "-J garbage" in "<curdir>"
+make: warning: Invalid internal option "-J" in "<curdir>"; see the manual page
+ in make[2] in directory "<curdir>"
+direct-open: mode=compat
+make: warning: Invalid internal option "-J" in "<curdir>"; see the manual page
+ in make[2] in directory "<curdir>"
+indirect-open: mode=compat
+indirect-expr: mode=parallel
+make: warning: Invalid internal option "-J" in "<curdir>"; see the manual page
+ in make[2] in directory "<curdir>"
+indirect-comment: mode=compat
+indirect-silent-comment: mode=parallel
+indirect-expr-empty: mode=parallel
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-jobs-internal.mk b/contrib/bmake/unit-tests/opt-jobs-internal.mk
index 44755a797751..13db820f86c1 100644
--- a/contrib/bmake/unit-tests/opt-jobs-internal.mk
+++ b/contrib/bmake/unit-tests/opt-jobs-internal.mk
@@ -1,9 +1,65 @@
-# $NetBSD: opt-jobs-internal.mk,v 1.3 2022/01/23 16:09:38 rillig Exp $
+# $NetBSD: opt-jobs-internal.mk,v 1.6 2025/05/23 21:05:56 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.
+# Tests for the (intentionally undocumented) internal -J command line option.
+
+# This test expects
+.MAKE.ALWAYS_PASS_JOB_QUEUE= no
+
+all: .PHONY
+ @${MAKE} -f ${MAKEFILE} -j1 direct
+ @${MAKE} -f ${MAKEFILE} -j1 direct-syntax
+ @${MAKE} -f ${MAKEFILE} -j1 direct-open
+ @${MAKE} -f ${MAKEFILE} -j1 indirect-open
+ @${MAKE} -f ${MAKEFILE} -j1 indirect-expr
+ @${MAKE} -f ${MAKEFILE} -j1 indirect-comment
+ @${MAKE} -f ${MAKEFILE} -j1 indirect-silent-comment
+ @${MAKE} -f ${MAKEFILE} -j1 indirect-expr-empty
+
+detect-mode: .PHONY
+ @mode=parallel
+ @echo ${HEADING}: mode=$${mode:-compat}
+
+# expect: direct: mode=parallel
+direct: .PHONY
+ @mode=parallel
+ @echo ${.TARGET}: mode=$${mode:-compat}
+
+# expect: make: error: invalid internal option "-J garbage" in "<curdir>"
+direct-syntax: .PHONY
+ @${MAKE} -f ${MAKEFILE} -J garbage unexpected-target || :
+
+# expect: direct-open: mode=compat
+direct-open: .PHONY
+ @${MAKE} -f ${MAKEFILE} -J 31,32 detect-mode HEADING=${.TARGET}
+
+# expect: indirect-open: mode=compat
+indirect-open: .PHONY
+ @${MAKE:U} -f ${MAKEFILE} detect-mode HEADING=${.TARGET}
+
+# When a command in its unexpanded form contains the expression "${MAKE}"
+# without any modifiers, the file descriptors get passed around.
+# expect: indirect-expr: mode=parallel
+indirect-expr: .PHONY
+ @${MAKE} -f ${MAKEFILE} detect-mode HEADING=${.TARGET}
+
+# The "# make" comment starts directly after the leading tab and is thus not
+# considered a shell command line. No file descriptors are passed around.
+# expect: indirect-comment: mode=compat
+indirect-comment: .PHONY
+ # make
+ @${MAKE:U} -f ${MAKEFILE} detect-mode HEADING=${.TARGET}
+
+# When the "# make" comment is prefixed with "@", it becomes a shell command.
+# As that shell command contains the plain word "make", the file descriptors
+# get passed around.
+# expect: indirect-silent-comment: mode=parallel
+indirect-silent-comment: .PHONY
+ @# make
+ @${MAKE:U} -f ${MAKEFILE} detect-mode HEADING=${.TARGET}
-# expect: make: internal error -- J option malformed (garbage)
-.MAKEFLAGS: -Jgarbage
+# When a command in its unexpanded form contains the plain word "make", the
+# file descriptors get passed around.
+# expect: indirect-expr-empty: mode=parallel
+indirect-expr-empty: .PHONY
+ @${:D make}
+ @${MAKE:U} -f ${MAKEFILE} detect-mode HEADING=${.TARGET}
diff --git a/contrib/bmake/unit-tests/opt-jobs-no-action.mk b/contrib/bmake/unit-tests/opt-jobs-no-action.mk
index 19d82c5bf4b8..fe720c9e0e61 100644
--- a/contrib/bmake/unit-tests/opt-jobs-no-action.mk
+++ b/contrib/bmake/unit-tests/opt-jobs-no-action.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-jobs-no-action.mk,v 1.9 2021/04/04 09:58:51 rillig Exp $
+# $NetBSD: opt-jobs-no-action.mk,v 1.10 2022/05/08 06:51:27 rillig Exp $
#
# Tests for the combination of the options -j and -n, which prints the
# commands instead of actually running them.
@@ -21,7 +21,7 @@
# The shell attributes are handled by Job_ParseShell.
# The shell attributes 'quiet' and 'echo' don't need a trailing newline,
# this is handled by the [0] != '\0' checks in Job_ParseShell.
-# The '\#' is handled by ParseGetLine.
+# The '\#' is handled by ParseRawLine.
# The '\n' is handled by Str_Words in Job_ParseShell.
# The '$$' is handled by Var_Subst in ParseDependencyLine.
.SHELL: \
diff --git a/contrib/bmake/unit-tests/opt-jobs.mk b/contrib/bmake/unit-tests/opt-jobs.mk
index 7d54d08a8421..818a4844e94b 100644
--- a/contrib/bmake/unit-tests/opt-jobs.mk
+++ b/contrib/bmake/unit-tests/opt-jobs.mk
@@ -1,8 +1,54 @@
-# $NetBSD: opt-jobs.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: opt-jobs.mk,v 1.7 2025/05/20 17:56:40 sjg Exp $
#
-# Tests for the -j command line option.
+# Tests for the -j command line option, which creates the targets in parallel.
-# TODO: Implementation
-all:
- @:;
+# The option '-j <integer>' specifies the number of targets that can be made
+# in parallel.
+ARGS= 0 1 2 8 08 017 0x10 -5 1000
+EXPECT.0= argument '0' to option '-j' must be a positive number (exit 2)
+EXPECT.1= 1
+EXPECT.2= 2
+EXPECT.8= 8
+EXPECT.08= argument '08' to option '-j' must be a positive number (exit 2)
+EXPECT.017= 15
+EXPECT.0x10= 16
+EXPECT.-5= argument '-5' to option '-j' must be a positive number (exit 2)
+EXPECT.1000= 1000
+
+.for arg in ${ARGS}
+OUTPUT!= ${MAKE} -r -f /dev/null -j ${arg} -v .MAKE.JOBS 2>&1 || echo "(exit $$?)"
+. if ${OUTPUT:[2..-1]} != ${EXPECT.${arg}}
+. warning ${arg}:${.newline} have: ${OUTPUT:[2..-1]}${.newline} want: ${EXPECT.${arg}}
+. endif
+.endfor
+
+
+# The options '-j <float>' and '-j <integer>C' multiply the given number with
+# the number of available CPUs.
+ARGS= 0.0 0C 0.0C .00001 .00001C 1C 1CPUs 1.2 .5e1C 07.5C 08.5C
+EXPECT.0.0= argument '0.0' to option '-j' must be a positive number (exit 2)
+EXPECT.0C= <integer> # rounded up to 1C
+EXPECT.0.0C= argument '0.0C' to option '-j' must be a positive number (exit 2)
+EXPECT..00001= argument '.00001' to option '-j' must be a positive number (exit 2)
+EXPECT..00001C= argument '.00001C' to option '-j' must be a positive number (exit 2)
+EXPECT.1C= <integer>
+EXPECT.1CPUs= <integer>
+EXPECT.1.2= <integer>
+EXPECT..5e1C= <integer> # unlikely to occur in practice
+EXPECT.07.5C= <integer>
+EXPECT.08.5C= argument '08.5C' to option '-j' must be a positive number (exit 2)
+
+.if ${.MAKE.JOBS.C} == "yes"
+. for arg in ${ARGS}
+OUTPUT!= ${MAKE} -r -f /dev/null -j ${arg} -v .MAKE.JOBS 2>&1 || echo "(exit $$?)"
+. if ${OUTPUT:C,^[0-9]+$,numeric,W} == numeric
+OUTPUT= <integer>
+. endif
+. if ${OUTPUT:[2..-1]} != ${EXPECT.${arg}}
+. warning ${arg}:${.newline} have: ${OUTPUT:[2..-1]}${.newline} want: ${EXPECT.${arg}}
+. endif
+. endfor
+.endif
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/opt-keep-going-indirect.exp b/contrib/bmake/unit-tests/opt-keep-going-indirect.exp
index 0c00c75395fa..4ac1b180cabd 100644
--- a/contrib/bmake/unit-tests/opt-keep-going-indirect.exp
+++ b/contrib/bmake/unit-tests/opt-keep-going-indirect.exp
@@ -3,14 +3,14 @@ false
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "direct" in unit-tests
exited 1
direct jobs
false
*** [direct] Error code 1
-make: stopped in unit-tests
+make: stopped making "direct" in unit-tests
exited 1
indirect compat
@@ -19,14 +19,14 @@ false
`indirect' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "indirect" in unit-tests
exited 1
indirect jobs
false
*** [direct] Error code 1
-make: stopped in unit-tests
+make: stopped making "indirect" in unit-tests
exited 1
exit status 0
diff --git a/contrib/bmake/unit-tests/opt-keep-going-indirect.mk b/contrib/bmake/unit-tests/opt-keep-going-indirect.mk
index 22f7be945f71..5d18553fa512 100644
--- a/contrib/bmake/unit-tests/opt-keep-going-indirect.mk
+++ b/contrib/bmake/unit-tests/opt-keep-going-indirect.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-keep-going-indirect.mk,v 1.2 2022/02/12 20:05:36 rillig Exp $
+# $NetBSD: opt-keep-going-indirect.mk,v 1.3 2024/04/02 15:05:15 rillig Exp $
#
# Tests for the -k command line option, which stops building a target as soon
# as an error is detected, but continues building the other, independent
@@ -49,19 +49,19 @@
# to the child processes.
all:
@echo 'direct compat'
- @set +e; env -i ${MAKE} -r -f ${MAKEFILE} -k direct; echo "exited $$?"
+ @set +e; env -i "PATH=$$PATH" ${MAKE} -r -f ${MAKEFILE} -k direct; echo "exited $$?"
@echo
@echo 'direct jobs'
- @set +e; env -i ${MAKE} -r -f ${MAKEFILE} -k direct -j1; echo "exited $$?"
+ @set +e; env -i "PATH=$$PATH" ${MAKE} -r -f ${MAKEFILE} -k direct -j1; echo "exited $$?"
@echo
@echo 'indirect compat'
- @set +e; env -i ${MAKE} -r -f ${MAKEFILE} -k indirect; echo "exited $$?"
+ @set +e; env -i "PATH=$$PATH" ${MAKE} -r -f ${MAKEFILE} -k indirect; echo "exited $$?"
@echo
@echo 'indirect jobs'
- @set +e; env -i ${MAKE} -r -f ${MAKEFILE} -k indirect -j1; echo "exited $$?"
+ @set +e; env -i "PATH=$$PATH" ${MAKE} -r -f ${MAKEFILE} -k indirect -j1; echo "exited $$?"
@echo
indirect: direct
diff --git a/contrib/bmake/unit-tests/opt-keep-going-multiple.exp b/contrib/bmake/unit-tests/opt-keep-going-multiple.exp
index 6d1bec18977b..00f944be735e 100644
--- a/contrib/bmake/unit-tests/opt-keep-going-multiple.exp
+++ b/contrib/bmake/unit-tests/opt-keep-going-multiple.exp
@@ -5,5 +5,5 @@ false fail2
true succeed
Stop.
-make: stopped in unit-tests
+make: stopped making "fail1 fail2 succeed" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/opt-keep-going.exp b/contrib/bmake/unit-tests/opt-keep-going.exp
index 2dbeb9927a30..06332333d355 100644
--- a/contrib/bmake/unit-tests/opt-keep-going.exp
+++ b/contrib/bmake/unit-tests/opt-keep-going.exp
@@ -5,5 +5,5 @@ other 1
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/opt-m-include-dir.mk b/contrib/bmake/unit-tests/opt-m-include-dir.mk
index 6e0801390395..b677f172e85b 100644
--- a/contrib/bmake/unit-tests/opt-m-include-dir.mk
+++ b/contrib/bmake/unit-tests/opt-m-include-dir.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-m-include-dir.mk,v 1.4 2020/09/01 20:14:34 rillig Exp $
+# $NetBSD: opt-m-include-dir.mk,v 1.5 2024/04/30 16:13:34 sjg Exp $
#
# Tests for the -m command line option, which adds a directory to the
# search path for the .include <...> directive.
@@ -22,11 +22,14 @@
TEST_DIR:= ${.PARSEFILE:R}.tmp/sub/sub/sub/workdir
CANARY_FILE:= ${.PARSEFILE:R}.tmp/sub/opt-m-canary.mk
ACTUAL_FILE:= ${.PARSEFILE:R}.tmp/sub/opt-m-step3.mk
+WANTED_FILE:= ${.PARSEFILE:R}.tmp/sub/opt-m-check.mk
_!= mkdir -p ${TEST_DIR}
_!= > ${CANARY_FILE}
_!= cp ${MAKEFILE} ${TEST_DIR}/step2.mk
_!= cp ${MAKEFILE} ${ACTUAL_FILE}
+_!= echo CHECK=ok > ${WANTED_FILE}
+_!= echo CHECK=${WANTED_FILE:T} found in .CURDIR > ${TEST_DIR}/${WANTED_FILE:T}
step1:
@${.MAKE} -C ${TEST_DIR} -f step2.mk step2
@@ -52,9 +55,10 @@ step1:
.elif ${.PARSEFILE:T} == "opt-m-step3.mk"
# This file is included by step2.mk.
+.include <opt-m-check.mk>
step2:
- @echo ok
+ @echo ${CHECK}
.else
. error
diff --git a/contrib/bmake/unit-tests/opt-query.exp b/contrib/bmake/unit-tests/opt-query.exp
index 38025dcf4d3a..0ba62780d844 100644
--- a/contrib/bmake/unit-tests/opt-query.exp
+++ b/contrib/bmake/unit-tests/opt-query.exp
@@ -1,2 +1,24 @@
+Making commands:
command during parsing
-exit status 1
+commands: query status 1
+
+Making opt-query-file.out-of-date in compat mode:
+opt-query-file.out-of-date in compat mode: query status 1
+
+Making opt-query-file.up-to-date in compat mode:
+`opt-query-file.up-to-date' is up to date.
+opt-query-file.up-to-date in compat mode: query status 0
+
+Making phony in compat mode:
+phony in compat mode: query status 1
+
+Making opt-query-file.out-of-date in jobs mode:
+opt-query-file.out-of-date in jobs mode: query status 1
+
+Making opt-query-file.up-to-date in jobs mode:
+opt-query-file.up-to-date in jobs mode: query status 0
+
+Making phony in jobs mode:
+phony in jobs mode: query status 1
+
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-query.mk b/contrib/bmake/unit-tests/opt-query.mk
index 0a7d5219a8fe..3554d69afad6 100644
--- a/contrib/bmake/unit-tests/opt-query.mk
+++ b/contrib/bmake/unit-tests/opt-query.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-query.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $
+# $NetBSD: opt-query.mk,v 1.7 2022/08/18 05:37:05 rillig Exp $
#
# Tests for the -q command line option.
#
@@ -6,7 +6,57 @@
# None of the commands in the targets are run, not even those that are
# prefixed with '+'.
-.MAKEFLAGS: -q
+# This test consists of several parts:
+#
+# main Delegates to the actual tests.
+#
+# commands Ensures that none of the targets is made.
+#
+# variants Ensures that the up-to-date status is correctly
+# reported in both compat and jobs mode, and for several
+# kinds of make targets.
+PART?= main
+
+.if ${PART} == "main"
+
+all: .PHONY variants cleanup
+
+_!= touch -f opt-query-file.up-to-date
+
+variants: .PHONY
+
+. for target in commands
+ @echo 'Making ${target}':
+ @${MAKE} -r -f ${MAKEFILE} -q ${mode:Mjobs:%=-j1} ${target} PART=commands \
+ && echo "${target}: query status $$?" \
+ || echo "${target}: query status $$?"
+ @echo
+. endfor
+
+. for mode in compat jobs
+. for target in opt-query-file.out-of-date opt-query-file.up-to-date phony
+ @echo 'Making ${target} in ${mode} mode':
+ @${MAKE} -r -f ${MAKEFILE} -q ${mode:Mjobs:%=-j1} ${target} PART=variants \
+ && echo "${target} in ${mode} mode: query status $$?" \
+ || echo "${target} in ${mode} mode: query status $$?"
+ @echo
+. endfor
+. endfor
+
+# Between 1994 and before 2022-08-17, the exit status for '-q' was always 1,
+# the cause for that exit code varied over time though.
+#
+# expect: opt-query-file.out-of-date in compat mode: query status 1
+# expect: opt-query-file.up-to-date in compat mode: query status 0
+# expect: phony in compat mode: query status 1
+# expect: opt-query-file.out-of-date in jobs mode: query status 1
+# expect: opt-query-file.up-to-date in jobs mode: query status 0
+# expect: phony in jobs mode: query status 1
+
+cleanup: .PHONY
+ @rm -f opt-query-file.up-to-date
+
+.elif ${PART} == "commands"
# This command cannot be prevented from being run since it is used at parse
# time, and any later variable assignments may depend on its result.
@@ -18,9 +68,18 @@
@+echo '$@: run always'
# None of these commands are run.
-all:
+commands:
@echo '$@: hidden command'
@+echo '$@: run always'
-
-# The exit status 1 is because the "all" target has to be made, that is,
+# The exit status 1 is because the "commands" target has to be made, that is,
# it is not up-to-date.
+
+.elif ${PART} == "variants"
+
+opt-query-file.out-of-date: ${MAKEFILE}
+opt-query-file.up-to-date: ${MAKEFILE}
+phony: .PHONY
+
+.else
+. error Invalid part '${PART}'
+.endif
diff --git a/contrib/bmake/unit-tests/opt-touch-jobs.mk b/contrib/bmake/unit-tests/opt-touch-jobs.mk
index 6005ab49d125..8c9c25c59015 100644
--- a/contrib/bmake/unit-tests/opt-touch-jobs.mk
+++ b/contrib/bmake/unit-tests/opt-touch-jobs.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-touch-jobs.mk,v 1.2 2021/01/30 12:14:08 rillig Exp $
+# $NetBSD: opt-touch-jobs.mk,v 1.3 2025/05/18 06:24:27 rillig Exp $
#
# Tests for the -t command line option in jobs mode.
@@ -27,7 +27,7 @@ opt-touch-use: .USE
# Even though it is listed last, in the output it appears first.
# This is because it is the only node that actually needs to be run.
# The "is up to date" of the other nodes happens after all jobs have
-# finished, by Make_Run > MakePrintStatusList > MakePrintStatus.
+# finished, by Make_MakeParallel > MakePrintStatusList > MakePrintStatus.
opt-touch-make: .MAKE
: Making $@.
diff --git a/contrib/bmake/unit-tests/opt-tracefile.exp b/contrib/bmake/unit-tests/opt-tracefile.exp
index 0e815606d34f..202c3c1afe49 100644
--- a/contrib/bmake/unit-tests/opt-tracefile.exp
+++ b/contrib/bmake/unit-tests/opt-tracefile.exp
@@ -1,12 +1,12 @@
Making dependency1 from <nothing>.
Making dependency2 from <nothing>.
Making trace from dependency1 dependency2.
-0 BEG
-1 JOB
-1 DON
-1 JOB
-1 DON
-1 JOB
-1 DON
-0 END
+<timestamp> 0 BEG <make-pid> <curdir>
+<timestamp> 1 JOB <make-pid> <curdir> dependency1 <job-pid> --- OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS|OP_DEPS_FOUND|OP_MARK
+<timestamp> 1 DON <make-pid> <curdir> dependency1 <job-pid> --- OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS|OP_DEPS_FOUND|OP_MARK
+<timestamp> 1 JOB <make-pid> <curdir> dependency2 <job-pid> --- OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS|OP_DEPS_FOUND|OP_MARK
+<timestamp> 1 DON <make-pid> <curdir> dependency2 <job-pid> --- OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS|OP_DEPS_FOUND|OP_MARK
+<timestamp> 1 JOB <make-pid> <curdir> trace <job-pid> --- OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS|OP_DEPS_FOUND
+<timestamp> 1 DON <make-pid> <curdir> trace <job-pid> --- OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS|OP_DEPS_FOUND
+<timestamp> 0 END <make-pid> <curdir>
exit status 0
diff --git a/contrib/bmake/unit-tests/opt-tracefile.mk b/contrib/bmake/unit-tests/opt-tracefile.mk
index 291824680606..d94c8045c224 100644
--- a/contrib/bmake/unit-tests/opt-tracefile.mk
+++ b/contrib/bmake/unit-tests/opt-tracefile.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-tracefile.mk,v 1.5 2021/12/06 22:35:20 rillig Exp $
+# $NetBSD: opt-tracefile.mk,v 1.6 2025/05/09 18:38:40 rillig Exp $
#
# Tests for the command line option '-T', which in jobs mode appends a trace
# record to a trace log whenever a job is started or completed.
@@ -6,8 +6,7 @@
all: .PHONY
@rm -f opt-tracefile.log
@${MAKE} -f ${MAKEFILE} -j1 -Topt-tracefile.log trace
- # Remove timestamps, process IDs and directory paths.
- @awk '{ print $$2, $$3 }' opt-tracefile.log
+ @awk '{ $$1 = "<timestamp>"; $$4 = "<make-pid>"; if (NF >= 7) $$7 = "<job-pid>"; print }' opt-tracefile.log
@rm opt-tracefile.log
trace dependency1 dependency2: .PHONY
diff --git a/contrib/bmake/unit-tests/opt-version.mk b/contrib/bmake/unit-tests/opt-version.mk
index 51a4e8a1a0aa..cdba9180ec01 100644
--- a/contrib/bmake/unit-tests/opt-version.mk
+++ b/contrib/bmake/unit-tests/opt-version.mk
@@ -1,8 +1,8 @@
-# $NetBSD: opt-version.mk,v 1.1 2021/12/23 11:05:59 rillig Exp $
+# $NetBSD: opt-version.mk,v 1.2 2022/05/08 07:27:50 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.
+# Tests for the command line option '--version', which may be expected to
+# output 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
diff --git a/contrib/bmake/unit-tests/opt-warnings-as-errors.exp b/contrib/bmake/unit-tests/opt-warnings-as-errors.exp
index 1db56b753bed..4e95da911102 100644
--- a/contrib/bmake/unit-tests/opt-warnings-as-errors.exp
+++ b/contrib/bmake/unit-tests/opt-warnings-as-errors.exp
@@ -1,7 +1,7 @@
-make: "opt-warnings-as-errors.mk" line 12: warning: message 1
+make: opt-warnings-as-errors.mk:13: warning: message 1
make: parsing warnings being treated as errors
-make: "opt-warnings-as-errors.mk" line 13: warning: message 2
+make: opt-warnings-as-errors.mk:15: warning: message 2
parsing continues
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/opt-warnings-as-errors.mk b/contrib/bmake/unit-tests/opt-warnings-as-errors.mk
index c29343f960a7..3896dad10f1a 100644
--- a/contrib/bmake/unit-tests/opt-warnings-as-errors.mk
+++ b/contrib/bmake/unit-tests/opt-warnings-as-errors.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-warnings-as-errors.mk,v 1.5 2021/01/27 00:02:38 rillig Exp $
+# $NetBSD: opt-warnings-as-errors.mk,v 1.6 2023/06/01 20:56:35 rillig Exp $
#
# Tests for the -W command line option, which turns warnings into errors.
#
@@ -9,7 +9,9 @@
.MAKEFLAGS: -W
+# expect+1: warning: message 1
.warning message 1
+# expect+1: warning: message 2
.warning message 2
_!= echo 'parsing continues' 1>&2
diff --git a/contrib/bmake/unit-tests/opt-x-reduce-exported.exp b/contrib/bmake/unit-tests/opt-x-reduce-exported.exp
index 39a9383953dd..99570f2c30cb 100644
--- a/contrib/bmake/unit-tests/opt-x-reduce-exported.exp
+++ b/contrib/bmake/unit-tests/opt-x-reduce-exported.exp
@@ -1 +1,5 @@
+ordinary:
+BEFORE=before
+submake:
+BEFORE=before
exit status 0
diff --git a/contrib/bmake/unit-tests/opt-x-reduce-exported.mk b/contrib/bmake/unit-tests/opt-x-reduce-exported.mk
index 7ee8e7c7eff0..a42a85d21a53 100644
--- a/contrib/bmake/unit-tests/opt-x-reduce-exported.mk
+++ b/contrib/bmake/unit-tests/opt-x-reduce-exported.mk
@@ -1,8 +1,20 @@
-# $NetBSD: opt-x-reduce-exported.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: opt-x-reduce-exported.mk,v 1.3 2022/05/08 07:27:50 rillig Exp $
#
-# Tests for the -x command line option.
+# Tests for the -X command line option, which prevents variables passed on the
+# command line from being exported to the environment of child commands.
-# TODO: Implementation
+# The variable 'BEFORE' is exported, the variable 'AFTER' isn't.
+.MAKEFLAGS: BEFORE=before -X AFTER=after
-all:
- @:;
+all: .PHONY ordinary submake
+
+ordinary: .PHONY
+ @echo 'ordinary:'
+ @env | sort | grep -e '^BEFORE' -e '^AFTER'
+
+submake: .PHONY
+ @echo 'submake:'
+ @${MAKE} -r -f ${MAKEFILE} show-env
+
+show-env: .PHONY
+ @env | sort | grep -e '^BEFORE' -e '^AFTER'
diff --git a/contrib/bmake/unit-tests/opt.exp b/contrib/bmake/unit-tests/opt.exp
index 3c96cf25025f..daeecc8ca726 100644
--- a/contrib/bmake/unit-tests/opt.exp
+++ b/contrib/bmake/unit-tests/opt.exp
@@ -13,7 +13,7 @@ make: don't know how to make -f (continuing)
`/dev/null' is up to date.
Stop.
-make: stopped in unit-tests
+make: stopped making "-f /dev/null" in unit-tests
*** Error code 1 (ignored)
make -?
diff --git a/contrib/bmake/unit-tests/opt.mk b/contrib/bmake/unit-tests/opt.mk
index 0931a66d3d15..939d5ec35aeb 100644
--- a/contrib/bmake/unit-tests/opt.mk
+++ b/contrib/bmake/unit-tests/opt.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt.mk,v 1.6 2020/11/18 01:06:59 sjg Exp $
+# $NetBSD: opt.mk,v 1.7 2023/02/25 00:07:08 rillig Exp $
#
# Tests for the command line options.
@@ -7,7 +7,7 @@
all: .IGNORE
# The options from the top-level make are passed to the sub-makes via
# the environment variable MAKEFLAGS. This is where the " -r -k -d 0"
- # comes from. See MainParseArg.
+ # comes from. See MainParseOption.
${MAKE} -r -f /dev/null -V MAKEFLAGS
@echo
diff --git a/contrib/bmake/unit-tests/parse-var.mk b/contrib/bmake/unit-tests/parse-var.mk
index bd6c59f0e5cb..5c693f8efd1c 100644
--- a/contrib/bmake/unit-tests/parse-var.mk
+++ b/contrib/bmake/unit-tests/parse-var.mk
@@ -1,13 +1,129 @@
-# $NetBSD: parse-var.mk,v 1.1 2020/10/04 06:53:15 rillig Exp $
+# $NetBSD: parse-var.mk,v 1.10 2024/06/02 15:31:26 rillig Exp $
+#
+# Tests for parsing expressions.
+#
+# TODO: Add systematic tests for all of the below combinations.
+#
+# Written form:
+# short form
+# long form with braces endc == '}'
+# long form with parentheses endc == ')'
+# indirect modifiers endc == '\0'
+#
+# Based on:
+# undefined variable
+# global variable
+# command-line variable
+# environment variable
+# target-local variable
+# legacy variable '@F'
+#
+# VarEvalMode:
+# parse
+# parse-balanced
+# eval
+# eval-defined
+# eval-keep-undefined
+# eval-keep-dollar-and-undefined
+#
+# Global mode:
+# without -dL
+# with -dL
+#
+# Modifiers:
+# no
+# yes, stay undefined
+# convert to defined
+# indirect modifiers, involving changes to VarEvalMode
+#
+# Error conditions:
+# for the short form, EOF after the '$'
+# for the short form, each character
+# for the long forms, EOF right after '${'
+# for the long forms, EOF after the variable name
+# for the long forms, EOF after the ':'
+# for the long forms, EOF after parsing a modifier
+# for the long forms, ':}'
+# for each modifier: syntactic error
+# for each modifier: evaluation error
+#
+# Context:
+# in a condition, only operand, unquoted
+# in a condition, only operand, quoted
+# in a condition, left-hand side, unquoted
+# in a condition, left-hand side, quoted
+# in a condition, right-hand side, unquoted
+# in a condition, right-hand side, quoted
+# left-hand side of a variable assignment
+# right-hand side of a ':=' variable assignment
+# right-hand side of a '!=' variable assignment
+# shell command in a target
+# .info directive
+# dependency line
+# items in a .for loop
+# everywhere else Var_Parse is called
+#
+# Further influences:
+# multi-level evaluations like 'other=${OTHER}' with OTHER='$$ ${THIRD}'
+#
+# Effects:
+# How much does the parsing position advance (pp)?
+# What's the value of the expression (return value)?
+# What error messages are printed (Parse_Error)?
+# What no-effect error messages are printed (Error)?
+# What error messages should be printed but aren't?
+# What other side effects are there?
.MAKEFLAGS: -dL
-# In variable assignments, there may be spaces on the left-hand side of the
-# assignment, but only if they occur inside variable expressions.
+# In variable assignments, there may be spaces in the middle of the left-hand
+# side of the assignment, but only if they occur inside expressions.
+# Leading spaces (but not tabs) are possible but unusual.
+# Trailing spaces are common in some coding styles, others omit them.
VAR.${:U param }= value
.if ${VAR.${:U param }} != "value"
. error
.endif
-all:
- @:;
+# Since var.c 1.323 from 2020-07-26 18:11 and until var.c 1.1047 from
+# 2023-02-18, the exact way of parsing an expression with subexpressions
+# depended on whether the expression was actually evaluated or merely parsed.
+#
+# If it was evaluated, nested expressions were parsed correctly, parsing each
+# modifier according to its exact definition (see varmod.mk).
+#
+# If the expression was merely parsed but not evaluated (for example, because
+# its value would not influence the outcome of the condition, or during the
+# first pass of the ':@var@body@' modifier), and the expression contained a
+# modifier, and that modifier contained a nested expression, the nested
+# expression was not parsed correctly. Instead, make only counted the opening
+# and closing delimiters, which failed for nested modifiers with unbalanced
+# braces.
+
+#.MAKEFLAGS: -dcpv
+# Keep these braces outside the conditions below, to keep them simple to
+# understand. If the expression ${BRACE_PAIR:...} had been replaced with the
+# literal ${:U{}}, the '}' would have to be escaped, but not the '{'. This
+# asymmetry would have made the example even more complicated to understand.
+BRACE_PAIR= {}
+# In this test word, the below conditions will replace the '{{}' in the middle
+# with the string '<lbraces>'.
+BRACE_GROUP= {{{{}}}}
+
+# The inner ':S' modifier turns the word '{}' into '{{}'.
+# The outer ':S' modifier then replaces '{{}' with '<lbraces>'.
+# Due to the always-true condition '1', the outer expression is relevant and
+# is parsed correctly.
+.if 1 && ${BRACE_GROUP:S,${BRACE_PAIR:S,{,{{,},<lbraces>,}
+.endif
+# Due to the always-false condition '0', the outer expression is irrelevant.
+# In this case, in the parts of the outer ':S' modifier, the expression parser
+# only counted the braces, and since the inner expression '${BRACE_PAIR:...}'
+# contains more '{' than '}', parsing failed with the error message 'Unfinished
+# modifier for "BRACE_GROUP"'. Fixed in var.c 1.1047 from 2023-02-18.
+.if 0 && ${BRACE_GROUP:S,${BRACE_PAIR:S,{,{{,},<lbraces>,}
+.endif
+#.MAKEFLAGS: -d0
+
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/parse.exp b/contrib/bmake/unit-tests/parse.exp
index 5807f9c17e2c..4f97a9350550 100644
--- a/contrib/bmake/unit-tests/parse.exp
+++ b/contrib/bmake/unit-tests/parse.exp
@@ -1,5 +1,6 @@
-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: parse.mk:7: Invalid line "<<<<<< old"
+make: parse.mk:14: Invalid line ">>>>>> new"
+make: parse.mk:25: Invalid line "one-target ${:U }", expanded to "one-target "
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "Try_to_crash_FreeBSD.xxxxxxxxxxxxxxxxxx" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/parse.mk b/contrib/bmake/unit-tests/parse.mk
index 9dccc7f5b7ce..fb959a4a5e1b 100644
--- a/contrib/bmake/unit-tests/parse.mk
+++ b/contrib/bmake/unit-tests/parse.mk
@@ -1,14 +1,55 @@
-# $NetBSD: parse.mk,v 1.2 2022/01/22 17:10:51 rillig Exp $
+# $NetBSD: parse.mk,v 1.8 2025/06/28 22:39:29 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
+# expect+1: Invalid line "<<<<<< old"
<<<<<< 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
+# expect+1: Invalid line ">>>>>> new"
>>>>>> new
+
+
+# Since parse.c 1.578 from 2021-12-14 and before parse.c 1.681 from
+# 2022-07-24, if a line of a makefile could only be a dependency specification
+# but didn't contain any of the dependency operators ':', '!', '::' and its
+# expansion ended with a space, make read a single byte from the memory beyond
+# the expanded line's terminating '\0'.
+#
+# https://bugs.freebsd.org/265119
+# expect+1: Invalid line "one-target ${:U }", expanded to "one-target "
+one-target ${:U }
+
+
+# Since parse.c 1.656 from 2022-01-27 and before parse.c 1.662 from
+# 2022-02-05, there was an out-of-bounds read in Parse_IsVar when looking for
+# a variable assignment in a dependency line with trailing whitespace. Lines
+# without trailing whitespace were not affected. Global variable assignments
+# were guaranteed to have no trailing whitespace and were thus not affected.
+#
+# Try to reproduce some variants that may lead to a crash, depending on the
+# memory allocator. To get a crash, the terminating '\0' of the line must be
+# the last byte of a memory page. The expression '${:U}' forces this trailing
+# whitespace.
+
+# On FreeBSD x86_64, a crash could in some cases be forced using the following
+# line, which has length 47, and if the memory for the expanded line starts at
+# 0xXXXX_XXd0, the terminating '\0' may end up at 0xXXXX_Xfff:
+Try_to_crash_FreeBSD.xxxxxxxxxxxxxxxxxx: 12345 ${:U}
+
+# The following line has length 4095 after being expanded, so line[4095] ==
+# '\0'. If the line is
+# allocated on a page boundary and the following page is not mapped, this line
+# leads to a segmentation fault.
+${:U:range=511:@_@1234567@:ts.}: 12345 ${:U}
+
+# The following line has length 8191, so line[8191] == '\0'. If the line is
+# allocated on a page boundary and the following page is not mapped, this line
+# leads to a segmentation fault.
+${:U:range=1023:@_@1234567@:ts.}: 12345 ${:U}
+
+12345:
diff --git a/contrib/bmake/unit-tests/posix-execution.exp b/contrib/bmake/unit-tests/posix-execution.exp
new file mode 100644
index 000000000000..e455adaf98d7
--- /dev/null
+++ b/contrib/bmake/unit-tests/posix-execution.exp
@@ -0,0 +1,24 @@
+one-at-a-time: shell_variable is 'second'
+echo "prefixes: ignore errors"; exit 13
+prefixes: ignore errors
+*** Error code 13 (ignored)
+prefixes: no echo
+prefixes: always, no echo
+shell-e-option: before
+shell-e-option: after
+echo 'do-prefix-plus: a regular command'
+echo 'do-prefix-plus: prefixed by plus'
+do-prefix-plus: prefixed by plus
+echo 'do-prefix-plus: a regular command'
+{ echo 'do-prefix-plus: a regular command'
+} || exit $?
+echo 'do-prefix-plus: prefixed by plus'
+do-prefix-plus: prefixed by plus
+{ echo 'do-prefix-plus: a regular command'
+} || exit $?
+do-error-not-ignored: successful
+*** Error code 13 (continuing)
+
+Stop.
+make: stopped making "do-error-not-ignored" in unit-tests
+exit status 0
diff --git a/contrib/bmake/unit-tests/posix-execution.mk b/contrib/bmake/unit-tests/posix-execution.mk
new file mode 100644
index 000000000000..ef4a66d84102
--- /dev/null
+++ b/contrib/bmake/unit-tests/posix-execution.mk
@@ -0,0 +1,59 @@
+# $NetBSD: posix-execution.mk,v 1.1 2025/04/13 09:29:32 rillig Exp $
+#
+# https://pubs.opengroup.org/onlinepubs/9799919799/utilities/make.html#tag_20_76_13_03
+#
+
+.POSIX:
+
+
+# The target consists of two commands, which are executed separately.
+# The second command thus does not see the shell variable from the first
+# command.
+# expect: one-at-a-time: shell_variable is 'second'
+all: one-at-a-time
+one-at-a-time:
+ @shell_variable=first
+ @echo "$@: shell_variable is '$${shell_variable:-second}'"
+
+
+# expect: echo "prefixes: ignore errors"; exit 13
+# expect: prefixes: ignore errors
+# expect-not: echo "prefixes: no echo"
+# expect: prefixes: no echo
+# expect: prefixes: always, no echo
+all: prefixes
+prefixes:
+ -echo "$@: ignore errors"; exit 13
+ @echo "$@: no echo"
+ +@echo "$@: always, no echo"
+
+
+# Deviation from POSIX: The shell "-e" option is not in effect.
+# expect: shell-e-option: before
+# expect: shell-e-option: after
+all: shell-e-option
+shell-e-option:
+ @echo '$@: before'; false; echo '$@: after'
+
+
+# expect-not-matches: ^do%-prefix%-plus: a regular command
+# expect: do-prefix-plus: prefixed by plus
+# expect: do-prefix-plus: prefixed by plus
+all: prefix-plus
+prefix-plus:
+ @${MAKE} -f ${MAKEFILE} -n do-prefix-plus
+ @${MAKE} -f ${MAKEFILE} -n -j1 do-prefix-plus
+do-prefix-plus:
+ @echo '$@: a regular command'
+ @+echo '$@: prefixed by plus'
+ @echo '$@: a regular command'
+
+
+# expect: do-error-not-ignored: successful
+# expect-not: do-error-not-ignored: after an error
+all: error-not-ignored
+error-not-ignored:
+ @${MAKE} -f ${MAKEFILE} do-error-not-ignored || :
+do-error-not-ignored:
+ @echo '$@: successful'; exit 13
+ @echo '$@: after an error'
diff --git a/contrib/bmake/unit-tests/posix-expansion.exp b/contrib/bmake/unit-tests/posix-expansion.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/posix-expansion.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/posix-expansion.mk b/contrib/bmake/unit-tests/posix-expansion.mk
new file mode 100644
index 000000000000..59082d567b37
--- /dev/null
+++ b/contrib/bmake/unit-tests/posix-expansion.mk
@@ -0,0 +1,22 @@
+# $NetBSD: posix-expansion.mk,v 1.2 2025/04/13 09:34:43 rillig Exp $
+#
+# https://pubs.opengroup.org/onlinepubs/9799919799/utilities/make.html#tag_20_76_13_05
+#
+# In POSIX mode, when expanding an expression containing modifiers, the
+# modifiers specified in POSIX take precedence over the BSD-style modifiers.
+
+.POSIX:
+
+
+MOD_SUBST= S s from to
+# The modifier contains a "=" and is thus the POSIX modifier.
+.if ${MOD_SUBST:S=from=to=} != "from=to= s from to"
+. error
+.endif
+# The modifier does not contain a "=" and thus falls back to the BSD modifier.
+.if ${MOD_SUBST:S,from,to,} != "S s to to"
+. error
+.endif
+
+
+all:
diff --git a/contrib/bmake/unit-tests/posix-varassign.exp b/contrib/bmake/unit-tests/posix-varassign.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/posix-varassign.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/posix-varassign.mk b/contrib/bmake/unit-tests/posix-varassign.mk
new file mode 100644
index 000000000000..6b9b2f083e3e
--- /dev/null
+++ b/contrib/bmake/unit-tests/posix-varassign.mk
@@ -0,0 +1,79 @@
+# $NetBSD: posix-varassign.mk,v 1.1 2025/04/13 09:29:33 rillig Exp $
+#
+# https://pubs.opengroup.org/onlinepubs/9799919799/utilities/make.html#tag_20_76_13_05
+#
+# Test that variable assignments work in the same way as in default mode.
+#
+# The assignment operators "::=" and ":::=" are intentionally not supported,
+# as they would introduce the distinction between eagerly and lazily evaluated
+# macros, in addition to the eagerly and lazily evaluated assignments, and
+# this would add too much complexity to the user's mental model, for too
+# little benefit.
+
+.POSIX:
+
+
+VAR= value
+.if ${VAR} != "value"
+. error
+.endif
+
+
+# Deviation from POSIX: The "::=" assignment operator is not supported,
+# instead, the variable named "VAR:" is defined.
+VAR= before
+VAR::= posix-immediate-expansion
+.if ${VAR} != "before"
+. error
+.elif ${${:UVAR\:}} != "posix-immediate-expansion"
+. error
+.endif
+
+
+# Deviation from POSIX: The ":::=" assignment operator is not supported,
+# instead, the variable named "VAR::" is defined.
+VAR:::= posix-delayed-expansion
+.if ${VAR} != "before"
+. error
+.elif ${${:UVAR\:\:}} != "posix-delayed-expansion"
+. error
+.endif
+
+
+VAR!= echo from shell command
+.if ${VAR} != "from shell command"
+. error
+.endif
+
+
+VAR= value
+VAR?= fallback
+.if ${VAR} != "value"
+. error
+.endif
+.undef VAR
+VAR?= fallback
+.if ${VAR} != "fallback"
+. error
+.endif
+
+
+VAR= value
+VAR+= appended
+.if ${VAR} != "value appended"
+. error
+.endif
+
+
+# In POSIX mode, the ":=" assignment operator is available as well, even
+# though it is not specified by POSIX, due to the differences in existing
+# make implementations.
+REF= before
+VAR:= immediate ${REF}
+REF= after
+.if ${VAR} != "immediate before"
+. error
+.endif
+
+
+all:
diff --git a/contrib/bmake/unit-tests/posix.exp b/contrib/bmake/unit-tests/posix.exp
index 01961f363f59..0d299fcca8d3 100644
--- a/contrib/bmake/unit-tests/posix.exp
+++ b/contrib/bmake/unit-tests/posix.exp
@@ -1,26 +1,4 @@
-Posix says we should execute the command as if run by system(3)
-Expect 'Hello,' and 'World!'
-Hello,
-World!
-a command
-a command prefixed by '+' executes even with -n
-another command
-make -n
-echo a command
-echo "a command prefixed by '+' executes even with -n"
-a command prefixed by '+' executes even with -n
-echo another command
-make -n -j1
-{ echo a command
-} || exit $?
-echo "a command prefixed by '+' executes even with -n"
-a command prefixed by '+' executes even with -n
-{ echo another command
-} || exit $?
-Now we expect an error...
-*** Error code 1 (continuing)
-`all' not remade because of errors.
+make: no target to make.
-Stop.
make: stopped in unit-tests
-exit status 1
+exit status 2
diff --git a/contrib/bmake/unit-tests/posix.mk b/contrib/bmake/unit-tests/posix.mk
index 43219258306e..5fe2ba9e1e07 100644
--- a/contrib/bmake/unit-tests/posix.mk
+++ b/contrib/bmake/unit-tests/posix.mk
@@ -1,23 +1,13 @@
-# $NetBSD: posix.mk,v 1.3 2022/01/23 18:15:29 rillig Exp $
-
-all: x plus subs err
-
-x:
- @echo "Posix says we should execute the command as if run by system(3)"
- @echo "Expect 'Hello,' and 'World!'"
- @echo Hello,; false; echo "World!"
-
-plus:
- @echo a command
- +@echo "a command prefixed by '+' executes even with -n"
- @echo another command
-
-subs:
- @echo make -n
- @${.MAKE} -r -f ${MAKEFILE} -n plus
- @echo make -n -j1
- @${.MAKE} -r -f ${MAKEFILE} -n -j1 plus
-
-err:
- @(echo Now we expect an error...; exit 1)
- @echo "Oops! you shouldn't see this!"
+# $NetBSD: posix.mk,v 1.5 2025/04/13 09:44:58 rillig Exp $
+#
+# This file is included in all tests that start with a ".POSIX:" line,
+# even when the "-r" option is given.
+
+# The makefile containing the POSIX definitions is not supposed to contain a
+# ".POSIX:" line, but even if it does, this must not lead to an endless loop
+# by including it over and over again.
+.POSIX:
+
+# The file <posix.mk> is not intended to be used as a top-level makefile, and
+# it is not supposed to define any targets, only rules.
+# expect: make: no target to make.
diff --git a/contrib/bmake/unit-tests/recursive.exp b/contrib/bmake/unit-tests/recursive.exp
index 36cd1c989532..e128d80beea6 100644
--- a/contrib/bmake/unit-tests/recursive.exp
+++ b/contrib/bmake/unit-tests/recursive.exp
@@ -1,5 +1,5 @@
-make: "recursive.mk" line 36: Unclosed variable "MISSING_PAREN"
-make: "recursive.mk" line 37: Unclosed variable "MISSING_BRACE"
+make: recursive.mk:38: Unclosed variable "MISSING_PAREN"
+make: recursive.mk:40: Unclosed variable "MISSING_BRACE"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/recursive.mk b/contrib/bmake/unit-tests/recursive.mk
index 5265cec59a2d..e1bde51138ca 100644
--- a/contrib/bmake/unit-tests/recursive.mk
+++ b/contrib/bmake/unit-tests/recursive.mk
@@ -1,4 +1,4 @@
-# $NetBSD: recursive.mk,v 1.5 2021/03/15 12:15:03 rillig Exp $
+# $NetBSD: recursive.mk,v 1.8 2024/06/02 15:31:26 rillig Exp $
#
# In -dL mode, a variable may get expanded before it makes sense.
# This would stop make from doing anything since the "recursive" error
@@ -6,24 +6,25 @@
#
# The purpose of evaluating that variable early was just to detect
# whether there are unclosed variables. The variable value is therefore
-# parsed with VARE_PARSE_ONLY for that purpose.
+# parsed with VARE_PARSE for that purpose.
#
-# Seen in pkgsrc/x11/libXfixes, and probably many more package that use
-# GNU Automake.
.MAKEFLAGS: -dL
+
AM_V_lt= ${am__v_lt_${V}}
am__v_lt_= ${am__v_lt_${AM_DEFAULT_VERBOSITY}}
am__v_lt_0= --silent
am__v_lt_1=
-# On 2020-08-06, make reported: "Variable am__v_lt_ is recursive."
+# Since parse.c 1.243 from 2020-07-31 and before parse.c 1.249 from
+# 2020-08-06, when make ran in -dL mode, it reported: "Variable am__v_lt_ is
+# recursive."
+#
+# Seen in pkgsrc/x11/libXfixes, and probably many more package that use
+# GNU Automake.
libXfixes_la_LINK= ... ${AM_V_lt} ...
-# somewhere later ...
-AM_DEFAULT_VERBOSITY= 1
-
# The purpose of the -dL flag is to detect unclosed variables. This
# can be achieved by just parsing the variable and not evaluating it.
@@ -33,6 +34,8 @@ AM_DEFAULT_VERBOSITY= 1
# therefore that's acceptable. In most practical cases, the missing
# brace would be detected directly in the line where it is produced.
MISSING_BRACE_INDIRECT:= ${:U\${MISSING_BRACE}
+# expect+1: Unclosed variable "MISSING_PAREN"
UNCLOSED= $(MISSING_PAREN
+# expect+1: Unclosed variable "MISSING_BRACE"
UNCLOSED= ${MISSING_BRACE
UNCLOSED= ${MISSING_BRACE_INDIRECT}
diff --git a/contrib/bmake/unit-tests/sh-dots.mk b/contrib/bmake/unit-tests/sh-dots.mk
index f85af9025e55..5294a4175b63 100755
--- a/contrib/bmake/unit-tests/sh-dots.mk
+++ b/contrib/bmake/unit-tests/sh-dots.mk
@@ -1,4 +1,4 @@
-# $NetBSD: sh-dots.mk,v 1.3 2020/10/25 22:04:24 rillig Exp $
+# $NetBSD: sh-dots.mk,v 1.4 2023/11/19 21:47:52 rillig Exp $
#
# Tests for the special shell command line "...", which does not run the
# commands below it but appends them to the list of commands that are run
@@ -29,8 +29,8 @@ commented: .IGNORE
... # Run the below commands later
@echo commented delayed ${.TARGET}
-# The dots don't have to be written literally, they can also come from a
-# variable expression.
+# The dots don't have to be written literally, they can also come from an
+# expression.
indirect:
@echo indirect regular
${:U...}
diff --git a/contrib/bmake/unit-tests/sh-errctl.exp b/contrib/bmake/unit-tests/sh-errctl.exp
index 8e6bc3c82125..8419d215fe38 100644
--- a/contrib/bmake/unit-tests/sh-errctl.exp
+++ b/contrib/bmake/unit-tests/sh-errctl.exp
@@ -1,6 +1,6 @@
job_pipe -1 -1, maxjobs 1, tokens 1, compat 0
-Job_TokenWithdraw(<pid>): aborting 0, running 0
-(<pid>) withdrew token
+TokenPool_Take: pid <pid>, aborting NONE, running 0
+TokenPool_Take: pid <pid> took a token
# echo off
echo silent
# echo on
@@ -16,12 +16,12 @@ set -e
echo always
Running all
Command: <shell>
-JobExec(all): pid <pid> added to jobs table
-job table @ job started
-job 0, status 3, flags ---, pid <pid>
+JobExec: target all, pid <pid> added to jobs table
+job started, job table:
+job 0, status running, flags ---, pid <pid>
silent
ignerr
always
-Job_TokenWithdraw(<pid>): aborting 0, running 0
-(<pid>) withdrew token
+TokenPool_Take: pid <pid>, aborting NONE, running 0
+TokenPool_Take: pid <pid> took a token
exit status 0
diff --git a/contrib/bmake/unit-tests/sh-jobs.exp b/contrib/bmake/unit-tests/sh-jobs.exp
index ef0c574fceed..25568145c049 100644
--- a/contrib/bmake/unit-tests/sh-jobs.exp
+++ b/contrib/bmake/unit-tests/sh-jobs.exp
@@ -2,5 +2,5 @@ comment-with-followup-line: This is printed.
no-comment: This is printed.
*** [no-comment] Error code 1
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/sh-leading-at.exp b/contrib/bmake/unit-tests/sh-leading-at.exp
index 8347fda085f7..8197a0d2b71a 100644
--- a/contrib/bmake/unit-tests/sh-leading-at.exp
+++ b/contrib/bmake/unit-tests/sh-leading-at.exp
@@ -3,4 +3,5 @@ space after @
echo 'echoed'
echoed
3
+whitespace in leading part
exit status 0
diff --git a/contrib/bmake/unit-tests/sh-leading-at.mk b/contrib/bmake/unit-tests/sh-leading-at.mk
index 9f98005ec088..cff3d4da1263 100644
--- a/contrib/bmake/unit-tests/sh-leading-at.mk
+++ b/contrib/bmake/unit-tests/sh-leading-at.mk
@@ -1,4 +1,4 @@
-# $NetBSD: sh-leading-at.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: sh-leading-at.mk,v 1.6 2023/01/19 19:55:27 rillig Exp $
#
# Tests for shell commands preceded by an '@', to suppress printing
# the command to stdout.
@@ -16,3 +16,7 @@ all:
# The leading '@' can be repeated.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@echo '3'
+
+ # Since 2023-01-17, the leading '@', '+' and '-' may contain
+ # whitespace, for compatibility with GNU make.
+ @ @ @ echo 'whitespace in leading part'
diff --git a/contrib/bmake/unit-tests/sh-leading-hyphen.exp b/contrib/bmake/unit-tests/sh-leading-hyphen.exp
index 39a9383953dd..50bcbbf9bb71 100644
--- a/contrib/bmake/unit-tests/sh-leading-hyphen.exp
+++ b/contrib/bmake/unit-tests/sh-leading-hyphen.exp
@@ -1 +1,11 @@
+true
+false
+*** Error code 1 (ignored)
+unknown-command 'needed for needshell in compat.c'
+unknown-command: not found
+*** Error code 127 (ignored)
+unknown-long-option 'needed for needshell in compat.c'
+unknown-long-option: not found
+*** Error code 127 (ignored)
+whitespace in leading part
exit status 0
diff --git a/contrib/bmake/unit-tests/sh-leading-hyphen.mk b/contrib/bmake/unit-tests/sh-leading-hyphen.mk
index d760abb9afdd..08b50a2ddc42 100644
--- a/contrib/bmake/unit-tests/sh-leading-hyphen.mk
+++ b/contrib/bmake/unit-tests/sh-leading-hyphen.mk
@@ -1,4 +1,4 @@
-# $NetBSD: sh-leading-hyphen.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: sh-leading-hyphen.mk,v 1.4 2023/01/19 19:55:27 rillig Exp $
#
# Tests for shell commands preceded by a '-', to ignore the exit status of
# the command line.
@@ -11,4 +11,19 @@
# TODO: Implementation
all:
- @:;
+ -true
+ -false
+
+ # An undefined variable expands to an empty string, without warning.
+ # This is used in practice for prefixing tool names or for DESTDIR.
+ # The '-' before 'unknown' is interpreted by make as '.IGNORE' flag.
+ ${UNDEF}-unknown-command 'needed for needshell in compat.c'
+
+ # Expanding undefined variables may lead to strange error messages
+ # when the shell interprets single-character options as commands
+ # instead.
+ ${UNDEF} --unknown-long-option 'needed for needshell in compat.c'
+
+ # Since 2023-01-17, the leading '@', '+' and '-' may contain
+ # whitespace, for compatibility with GNU make.
+ - - - @echo 'whitespace in leading part'
diff --git a/contrib/bmake/unit-tests/sh-leading-plus.exp b/contrib/bmake/unit-tests/sh-leading-plus.exp
index eb586d29f1c2..8cc26deaacb5 100644
--- a/contrib/bmake/unit-tests/sh-leading-plus.exp
+++ b/contrib/bmake/unit-tests/sh-leading-plus.exp
@@ -1,4 +1,6 @@
echo 'this command is not run'
echo 'this command is run'
this command is run
+echo 'whitespace in leading part'
+whitespace in leading part
exit status 0
diff --git a/contrib/bmake/unit-tests/sh-leading-plus.mk b/contrib/bmake/unit-tests/sh-leading-plus.mk
index ff57b4a38a7d..83e7e7a15e24 100644
--- a/contrib/bmake/unit-tests/sh-leading-plus.mk
+++ b/contrib/bmake/unit-tests/sh-leading-plus.mk
@@ -1,4 +1,4 @@
-# $NetBSD: sh-leading-plus.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $
+# $NetBSD: sh-leading-plus.mk,v 1.5 2023/01/19 19:55:27 rillig Exp $
#
# Tests for shell commands preceded by a '+', to run them even if
# the command line option -n is given.
@@ -8,3 +8,7 @@
all:
@echo 'this command is not run'
@+echo 'this command is run'
+
+ # Since 2023-01-17, the leading '@', '+' and '-' may contain
+ # whitespace, for compatibility with GNU make.
+ + + + @echo 'whitespace in leading part'
diff --git a/contrib/bmake/unit-tests/shell-csh.mk b/contrib/bmake/unit-tests/shell-csh.mk
index 47313563d22b..cfe28199dbd2 100644
--- a/contrib/bmake/unit-tests/shell-csh.mk
+++ b/contrib/bmake/unit-tests/shell-csh.mk
@@ -1,4 +1,4 @@
-# $NetBSD: shell-csh.mk,v 1.8 2021/04/04 09:58:51 rillig Exp $
+# $NetBSD: shell-csh.mk,v 1.10 2025/06/05 21:56:54 rillig Exp $
#
# Tests for using a C shell for running the commands.
@@ -6,7 +6,7 @@ CSH!= which csh 2> /dev/null || true
# The shell path must be an absolute path.
# This is only obvious in parallel mode since in compat mode,
-# simple commands are executed via execve directly.
+# simple commands are executed via execvp directly.
.if ${CSH} != ""
.SHELL: name="csh" path="${CSH}"
.endif
@@ -33,7 +33,9 @@ all:
-echo ignore errors
# In the C shell, "unset verbose" is set as the noPrint command.
- # Therefore it is filtered from the output, rather naively.
+ # Therefore, it is filtered from the output, rather naively.
+# FIXME: Don't assume a newline character in PrintFilteredOutput.
+# expect: They chatted in the sy.
@echo 'They chatted in the sunset verbosely.'
.else
@sed '$$d' ${MAKEFILE:.mk=.exp} # This is cheated.
diff --git a/contrib/bmake/unit-tests/shell-ksh.exp b/contrib/bmake/unit-tests/shell-ksh.exp
index 0bf83203a23a..fcb9fb888d21 100644
--- a/contrib/bmake/unit-tests/shell-ksh.exp
+++ b/contrib/bmake/unit-tests/shell-ksh.exp
@@ -1,4 +1,9 @@
-: normal
-: always
-: ignore errors
+echo normal
+normal
+hidden
+echo always
+always
+echo ignore errors
+ignore errors
+The "is filtered out.
exit status 0
diff --git a/contrib/bmake/unit-tests/shell-ksh.mk b/contrib/bmake/unit-tests/shell-ksh.mk
index 3acf98cdb5d1..676c8e2d47d9 100644
--- a/contrib/bmake/unit-tests/shell-ksh.mk
+++ b/contrib/bmake/unit-tests/shell-ksh.mk
@@ -1,11 +1,39 @@
-# $NetBSD: shell-ksh.mk,v 1.1 2020/10/03 14:39:36 rillig Exp $
+# $NetBSD: shell-ksh.mk,v 1.2 2025/06/05 21:56:54 rillig Exp $
#
-# Tests for using a korn shell for running the commands.
+# Tests for using a Korn shell for running the commands.
-.SHELL: name="ksh" path="ksh"
+KSH!= which ksh 2> /dev/null || true
+
+# The shell path must be an absolute path.
+# This is only obvious in parallel mode since in compat mode,
+# simple commands are executed via execvp directly.
+.if ${KSH} != ""
+.SHELL: name="ksh" path="${KSH}"
+.endif
+
+# In parallel mode, the shell->noPrint command is filtered from
+# the output, rather naively (in PrintOutput).
+.MAKEFLAGS: -j1
all:
- : normal
- @: hidden
- +: always
- -: ignore errors
+.if ${KSH} != ""
+ # This command is both printed and executed.
+ echo normal
+
+ # This command is only executed.
+ @echo hidden
+
+ # This command is both printed and executed.
+ +echo always
+
+ # This command is both printed and executed.
+ -echo ignore errors
+
+ # In the Korn shell, "set +v" is set as the noPrint command.
+ # Therefore, it is filtered from the output, rather naively.
+# FIXME: Don't assume a newline character in PrintFilteredOutput.
+# expect: The "is filtered out.
+ @echo 'The "set +v" is filtered out.'
+.else
+ @sed '$$d' ${MAKEFILE:.mk=.exp} # This is cheated.
+.endif
diff --git a/contrib/bmake/unit-tests/shell-sh.mk b/contrib/bmake/unit-tests/shell-sh.mk
index b3d4f18bbac9..5f7b04716ee1 100644
--- a/contrib/bmake/unit-tests/shell-sh.mk
+++ b/contrib/bmake/unit-tests/shell-sh.mk
@@ -1,9 +1,9 @@
-# $NetBSD: shell-sh.mk,v 1.1 2020/10/03 14:39:36 rillig Exp $
+# $NetBSD: shell-sh.mk,v 1.2 2023/12/24 16:48:30 sjg Exp $
#
# Tests for using a bourne shell for running the commands.
# This is the default shell, so there's nothing surprising.
-.SHELL: name="sh" path="sh"
+.SHELL: name="sh"
all:
: normal
diff --git a/contrib/bmake/unit-tests/suff-add-later.exp b/contrib/bmake/unit-tests/suff-add-later.exp
index 663016a672c1..ee19c851d57a 100644
--- a/contrib/bmake/unit-tests/suff-add-later.exp
+++ b/contrib/bmake/unit-tests/suff-add-later.exp
@@ -17,5 +17,5 @@ make: don't know how to make issue5e.d (continuing)
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/suff-clear-regular.exp b/contrib/bmake/unit-tests/suff-clear-regular.exp
index 75db9b47a55b..a14c722f35bb 100644
--- a/contrib/bmake/unit-tests/suff-clear-regular.exp
+++ b/contrib/bmake/unit-tests/suff-clear-regular.exp
@@ -4,5 +4,5 @@ make: don't know how to make .b.a (continuing)
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/suff-clear-single.exp b/contrib/bmake/unit-tests/suff-clear-single.exp
index aa46ac75f6da..3a187abca478 100644
--- a/contrib/bmake/unit-tests/suff-clear-single.exp
+++ b/contrib/bmake/unit-tests/suff-clear-single.exp
@@ -2,5 +2,5 @@ make: don't know how to make issue3 (continuing)
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/suff-incomplete.exp b/contrib/bmake/unit-tests/suff-incomplete.exp
index acb5f0542dbe..e5e4a770b26d 100644
--- a/contrib/bmake/unit-tests/suff-incomplete.exp
+++ b/contrib/bmake/unit-tests/suff-incomplete.exp
@@ -1,29 +1,29 @@
-Parsing line 9: .SUFFIXES:
+Parsing suff-incomplete.mk:9: .SUFFIXES:
ParseDependency(.SUFFIXES:)
Clearing all suffixes
-Parsing line 11: .SUFFIXES: .a .b .c
+Parsing suff-incomplete.mk:11: .SUFFIXES: .a .b .c
ParseDependency(.SUFFIXES: .a .b .c)
Adding suffix ".a"
Adding suffix ".b"
Adding suffix ".c"
-Parsing line 17: .a.b:
+Parsing suff-incomplete.mk: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
-Parsing line 21: .a.c: ${.PREFIX}.dependency
+Parsing suff-incomplete.mk:21: .a.c: ${.PREFIX}.dependency
deleting incomplete transformation from `.a' to `.b'
ParseDependency(.a.c: ${.PREFIX}.dependency)
defining transformation from `.a' to `.c'
inserting ".a" (1) at end of list
inserting ".c" (3) at end of list
-# LinkSource: added child .a.c - ${.PREFIX}.dependency
+Target ".a.c" depends on "${.PREFIX}.dependency"
# .a.c, unmade, type OP_DEPENDS|OP_TRANSFORM, flags none
# ${.PREFIX}.dependency, unmade, type none, flags none
-Parsing line 23: .DEFAULT:
+Parsing suff-incomplete.mk:23: .DEFAULT:
transformation .a.c complete
ParseDependency(.DEFAULT:)
-Parsing line 24: : Making ${.TARGET} from ${.IMPSRC} all ${.ALLSRC} by default.
+Parsing suff-incomplete.mk: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 4ffb86e65fa0..bb1fc91ea21d 100644
--- a/contrib/bmake/unit-tests/suff-main-several.exp
+++ b/contrib/bmake/unit-tests/suff-main-several.exp
@@ -1,11 +1,11 @@
-Parsing line 8: .1.2 .1.3 .1.4:
+Parsing suff-main-several.mk:8: .1.2 .1.3 .1.4:
ParseDependency(.1.2 .1.3 .1.4:)
Setting main node to ".1.2"
-Parsing line 9: : Making ${.TARGET} from ${.IMPSRC}.
-Parsing line 14: next-main:
+Parsing suff-main-several.mk:9: : Making ${.TARGET} from ${.IMPSRC}.
+Parsing suff-main-several.mk:14: next-main:
ParseDependency(next-main:)
-Parsing line 15: : Making ${.TARGET}
-Parsing line 19: .SUFFIXES: .1 .2 .3 .4
+Parsing suff-main-several.mk:15: : Making ${.TARGET}
+Parsing suff-main-several.mk:19: .SUFFIXES: .1 .2 .3 .4
ParseDependency(.SUFFIXES: .1 .2 .3 .4)
Adding suffix ".1"
Adding suffix ".2"
@@ -26,44 +26,44 @@ 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"
-Parsing line 24: .SUFFIXES:
+Parsing suff-main-several.mk:24: .SUFFIXES:
ParseDependency(.SUFFIXES:)
Clearing all suffixes
-Parsing line 32: .SUFFIXES: .4 .3 .2 .1
+Parsing suff-main-several.mk:32: .SUFFIXES: .4 .3 .2 .1
ParseDependency(.SUFFIXES: .4 .3 .2 .1)
Adding suffix ".4"
Adding suffix ".3"
Adding suffix ".2"
Adding suffix ".1"
-Parsing line 33: .SUFFIXES:
+Parsing suff-main-several.mk:33: .SUFFIXES:
ParseDependency(.SUFFIXES:)
Clearing all suffixes
-Parsing line 34: .SUFFIXES: .1 .2 .3 .4
+Parsing suff-main-several.mk:34: .SUFFIXES: .1 .2 .3 .4
ParseDependency(.SUFFIXES: .1 .2 .3 .4)
Adding suffix ".1"
Adding suffix ".2"
Adding suffix ".3"
Adding suffix ".4"
-Parsing line 35: .SUFFIXES:
+Parsing suff-main-several.mk:35: .SUFFIXES:
ParseDependency(.SUFFIXES:)
Clearing all suffixes
-Parsing line 36: .SUFFIXES: .4 .3 .2 .1
+Parsing suff-main-several.mk:36: .SUFFIXES: .4 .3 .2 .1
ParseDependency(.SUFFIXES: .4 .3 .2 .1)
Adding suffix ".4"
Adding suffix ".3"
Adding suffix ".2"
Adding suffix ".1"
-Parsing line 38: suff-main-several.1:
+Parsing suff-main-several.mk:38: suff-main-several.1:
ParseDependency(suff-main-several.1:)
-Parsing line 39: : Making ${.TARGET} out of nothing.
-Parsing line 40: next-main: suff-main-several.{2,3,4}
+Parsing suff-main-several.mk:39: : Making ${.TARGET} out of nothing.
+Parsing suff-main-several.mk: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}
+Target "next-main" depends on "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
-Parsing line 42: .MAKEFLAGS: -d0 -dg1
+Parsing suff-main-several.mk:42: .MAKEFLAGS: -d0 -dg1
ParseDependency(.MAKEFLAGS: -d0 -dg1)
-#*** Input graph:
+#*** Begin input graph for pass 1 in <curdir>:
# .1.2, unmade, type OP_TRANSFORM, flags none
# .1.3, unmade, type OP_TRANSFORM, flags none
# .1.4, unmade, type OP_TRANSFORM, flags none
@@ -86,7 +86,9 @@ ParseDependency(.MAKEFLAGS: -d0 -dg1)
.MAKE = <details omitted>
.MAKE.DEPENDFILE = <details omitted>
.MAKE.GID = <details omitted>
+.MAKE.JOBS.C = <details omitted>
.MAKE.LEVEL = <details omitted>
+.MAKE.LEVEL.ENV = MAKELEVEL
.MAKE.MAKEFILES = <details omitted>
.MAKE.MAKEFILE_PREFERENCE = <details omitted>
.MAKE.OS = <details omitted>
@@ -105,7 +107,6 @@ MACHINE_ARCH = <details omitted>
MAKE = <details omitted>
MFLAGS = -r -k -d mps -d 0 -d g1
#*** Command-line Variables:
-.MAKE.LEVEL.ENV = MAKELEVEL
#*** Directory Cache:
# Stats: 0 hits 2 misses 0 near misses 0 losers (0%)
@@ -130,11 +131,12 @@ MFLAGS = -r -k -d mps -d 0 -d g1
# From:
# Search Path:
#*** Transformations:
+#*** End input graph for pass 1 in <curdir>:
make: don't know how to make suff-main-several.2 (continuing)
make: don't know how to make suff-main-several.3 (continuing)
make: don't know how to make suff-main-several.4 (continuing)
`next-main' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "next-main" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/suff-rebuild.exp b/contrib/bmake/unit-tests/suff-rebuild.exp
index 8c0979537524..e44fa0fe9893 100644
--- a/contrib/bmake/unit-tests/suff-rebuild.exp
+++ b/contrib/bmake/unit-tests/suff-rebuild.exp
@@ -1,36 +1,36 @@
-Parsing line 10: .SUFFIXES:
+Parsing suff-rebuild.mk:10: .SUFFIXES:
ParseDependency(.SUFFIXES:)
Clearing all suffixes
-Parsing line 12: .SUFFIXES: .a .b .c
+Parsing suff-rebuild.mk:12: .SUFFIXES: .a .b .c
ParseDependency(.SUFFIXES: .a .b .c)
Adding suffix ".a"
Adding suffix ".b"
Adding suffix ".c"
-Parsing line 14: suff-rebuild-example.a:
+Parsing suff-rebuild.mk:14: suff-rebuild-example.a:
ParseDependency(suff-rebuild-example.a:)
Adding "suff-rebuild-example.a" to all targets.
-Parsing line 15: : Making ${.TARGET} out of nothing.
-Parsing line 17: .a.b:
+Parsing suff-rebuild.mk:15: : Making ${.TARGET} out of nothing.
+Parsing suff-rebuild.mk: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
-Parsing line 18: : Making ${.TARGET} from ${.IMPSRC}.
-Parsing line 19: .b.c:
+Parsing suff-rebuild.mk:18: : Making ${.TARGET} from ${.IMPSRC}.
+Parsing suff-rebuild.mk: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
-Parsing line 20: : Making ${.TARGET} from ${.IMPSRC}.
-Parsing line 21: .c:
+Parsing suff-rebuild.mk:20: : Making ${.TARGET} from ${.IMPSRC}.
+Parsing suff-rebuild.mk: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
-Parsing line 22: : Making ${.TARGET} from ${.IMPSRC}.
-Parsing line 44: .SUFFIXES: .c .b .a
+Parsing suff-rebuild.mk:22: : Making ${.TARGET} from ${.IMPSRC}.
+Parsing suff-rebuild.mk: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/suff-self.exp b/contrib/bmake/unit-tests/suff-self.exp
index 6192c508ab96..2fb3ac493513 100644
--- a/contrib/bmake/unit-tests/suff-self.exp
+++ b/contrib/bmake/unit-tests/suff-self.exp
@@ -2,5 +2,5 @@ make: Graph cycles through suff-self.suff
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/suff-transform-debug.exp b/contrib/bmake/unit-tests/suff-transform-debug.exp
index 737fb0484718..ad0584f204d1 100644
--- a/contrib/bmake/unit-tests/suff-transform-debug.exp
+++ b/contrib/bmake/unit-tests/suff-transform-debug.exp
@@ -1,4 +1,4 @@
-#*** Input graph:
+#*** Begin input graph for pass 1 in <curdir>:
# all, unmade, type OP_DEPENDS, flags none
@@ -12,7 +12,9 @@
.MAKE = <details omitted>
.MAKE.DEPENDFILE = <details omitted>
.MAKE.GID = <details omitted>
+.MAKE.JOBS.C = <details omitted>
.MAKE.LEVEL = <details omitted>
+.MAKE.LEVEL.ENV = MAKELEVEL
.MAKE.MAKEFILES = <details omitted>
.MAKE.MAKEFILE_PREFERENCE = <details omitted>
.MAKE.OS = <details omitted>
@@ -31,7 +33,6 @@ MACHINE_ARCH = <details omitted>
MAKE = <details omitted>
MFLAGS = -r -k -d g1
#*** Command-line Variables:
-.MAKE.LEVEL.ENV = MAKELEVEL
#*** Directory Cache:
# Stats: 0 hits 2 misses 0 near misses 0 losers (0%)
@@ -58,4 +59,5 @@ MFLAGS = -r -k -d g1
.cpp.a :
: Making ${.TARGET} from impsrc ${.IMPSRC} allsrc ${.ALLSRC}.
+#*** End input graph for pass 1 in <curdir>:
exit status 0
diff --git a/contrib/bmake/unit-tests/suff-transform-endless.exp b/contrib/bmake/unit-tests/suff-transform-endless.exp
index 620c46626e22..552d77355939 100644
--- a/contrib/bmake/unit-tests/suff-transform-endless.exp
+++ b/contrib/bmake/unit-tests/suff-transform-endless.exp
@@ -42,5 +42,5 @@ make: Graph cycles through issue6.f
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/suff-transform-expand.exp b/contrib/bmake/unit-tests/suff-transform-expand.exp
index c1821852707d..5ad429b8a852 100644
--- a/contrib/bmake/unit-tests/suff-transform-expand.exp
+++ b/contrib/bmake/unit-tests/suff-transform-expand.exp
@@ -4,5 +4,5 @@ make: don't know how to make .first (continuing)
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/suff-transform-select.exp b/contrib/bmake/unit-tests/suff-transform-select.exp
index 29065154c891..df852a603a99 100644
--- a/contrib/bmake/unit-tests/suff-transform-select.exp
+++ b/contrib/bmake/unit-tests/suff-transform-select.exp
@@ -43,5 +43,5 @@ make: don't know how to make issue10.e (continuing)
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/suff-use.exp b/contrib/bmake/unit-tests/suff-use.exp
index 4a9374d8e156..ea6a20ece9e7 100644
--- a/contrib/bmake/unit-tests/suff-use.exp
+++ b/contrib/bmake/unit-tests/suff-use.exp
@@ -3,5 +3,5 @@ make: don't know how to make demo.o (continuing)
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/suff.exp b/contrib/bmake/unit-tests/suff.exp
new file mode 100644
index 000000000000..adf646850d6e
--- /dev/null
+++ b/contrib/bmake/unit-tests/suff.exp
@@ -0,0 +1,146 @@
+Adding suffix ".from"
+Adding suffix ".to"
+defining transformation from `.from' to `.to'
+inserting ".from" (1) at end of list
+inserting ".to" (2) at end of list
+transformation .from.to complete
+Var_Parse: ${.PREFIX}${.ARCHIVE}.additional (eval)
+Var_Parse: ${.ARCHIVE}.additional (eval)
+Global: .ALLTARGETS = step1 edge-case.to everything ${.PREFIX}${.ARCHIVE}.additional
+Global: .ALLTARGETS = step1 edge-case.to everything ${.PREFIX}${.ARCHIVE}.additional edge-case.from
+Global: .ALLTARGETS = step1 edge-case.to everything ${.PREFIX}${.ARCHIVE}.additional edge-case.from edge-case.additional
+Global: .ALLTARGETS = step1 edge-case.to everything ${.PREFIX}${.ARCHIVE}.additional edge-case.from edge-case.additional a*.mk
+Global: delete .PARSEDIR
+Global: delete .PARSEFILE
+Global: ignoring delete '.INCLUDEDFROMDIR' as it is not found
+Global: ignoring delete '.INCLUDEDFROMFILE' as it is not found
+Var_Parse: ${.MAKE.DEPENDFILE} (eval)
+Var_Parse: ${.MAKE.MODE:tl} (eval)
+Evaluating modifier ${.MAKE.MODE:t...} on value "" (eval, undefined)
+Result of ${.MAKE.MODE:tl} is "" (eval, undefined)
+Global: MFLAGS = -r -k -d sv
+Var_Parse: ${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@} (eval)
+Var_Parse: ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@} (eval)
+Evaluating modifier ${.MAKEOVERRIDES:O} on value ""
+Result of ${.MAKEOVERRIDES:O} is ""
+Evaluating modifier ${.MAKEOVERRIDES:u} on value ""
+Result of ${.MAKEOVERRIDES:u} is ""
+Evaluating modifier ${.MAKEOVERRIDES:@...} on value ""
+Modifier part: "v"
+Modifier part: "$v=${$v:Q}"
+ModifyWords: split "" into 1 word
+Command: ignoring delete 'v' as it is not found
+Result of ${.MAKEOVERRIDES:@v@$v=${$v:Q}@} is ""
+Global: .INCLUDES = # (empty)
+Global: .LIBS = # (empty)
+Global: ignoring delete '.SHELL' as it is not found
+Command: .SHELL = /bin/sh
+Global: .ALLTARGETS = step1 edge-case.to everything ${.PREFIX}${.ARCHIVE}.additional edge-case.from edge-case.additional a*.mk .END
+step1: @ = step1
+step1: @ = step1
+step1: * = step1
+SuffFindDeps "step1"
+step1: @ = step1
+step1: * = step1
+ No valid suffix on step1
+Wildcard expanding "edge-case.to"...suffix is ".to"...
+edge-case.to: @ = edge-case.to
+edge-case.to: @ = edge-case.to
+edge-case.to: * = edge-case.to
+SuffFindDeps "edge-case.to"
+ trying edge-case.from...got it
+edge-case.to: @ = edge-case.to
+edge-case.to: * = edge-case
+Expanding "${.PREFIX}${.ARCHIVE}.additional"...Var_Parse: ${.PREFIX}${.ARCHIVE}.additional (eval)
+Var_Parse: ${.ARCHIVE}.additional (eval)
+edge-case.additional...
+ applying .from -> .to to "edge-case.to"
+everything: @ = everything
+everything: @ = everything
+everything: * = everything
+SuffFindDeps "everything"
+everything: @ = everything
+everything: * = everything
+Wildcard expanding "a*.mk"...
+archive-suffix.mk...Global: .ALLTARGETS = step1 edge-case.to everything ${.PREFIX}${.ARCHIVE}.additional edge-case.from edge-case.additional a*.mk .END archive-suffix.mk
+archive.mk...Global: .ALLTARGETS = step1 edge-case.to everything ${.PREFIX}${.ARCHIVE}.additional edge-case.from edge-case.additional a*.mk .END archive-suffix.mk archive.mk
+
+ No valid suffix on everything
+Wildcard expanding "edge-case.additional"...
+edge-case.additional: @ = edge-case.additional
+edge-case.additional: @ = edge-case.additional
+edge-case.additional: * = edge-case.additional
+SuffFindDeps "edge-case.additional"
+ No known suffix on edge-case.additional. Using .NULL suffix
+not adding suffix rules
+edge-case.additional: @ = edge-case.additional
+edge-case.additional: * = edge-case.additional
+suffix is ".from"...
+edge-case.from: @ = edge-case.from
+edge-case.from: @ = edge-case.from
+edge-case.from: * = edge-case.from
+SuffFindDeps "edge-case.from"
+edge-case.from: @ = edge-case.from
+edge-case.from: * = edge-case
+Wildcard expanding "archive-suffix.mk"...
+archive-suffix.mk: @ = archive-suffix.mk
+archive-suffix.mk: @ = archive-suffix.mk
+archive-suffix.mk: * = archive-suffix.mk
+SuffFindDeps "archive-suffix.mk"
+ No known suffix on archive-suffix.mk. Using .NULL suffix
+adding suffix rules
+archive-suffix.mk: @ = archive-suffix.mk
+archive-suffix.mk: * = archive-suffix.mk
+archive-suffix.mk: @ = archive-suffix.mk
+archive-suffix.mk: * = archive-suffix.mk
+Wildcard expanding "archive.mk"...
+archive.mk: @ = archive.mk
+archive.mk: @ = archive.mk
+archive.mk: * = archive.mk
+SuffFindDeps "archive.mk"
+ No known suffix on archive.mk. Using .NULL suffix
+adding suffix rules
+archive.mk: @ = archive.mk
+archive.mk: * = archive.mk
+archive.mk: @ = archive.mk
+archive.mk: * = archive.mk
+Wildcard expanding "edge-case.additional"...
+edge-case.additional: ? = # (empty)
+edge-case.additional: > = # (empty)
+Var_Parse: ${.TARGET} out of nothing. (eval)
+: Making edge-case.additional out of nothing.
+edge-case.to: < = edge-case.from
+suffix is ".from"...
+edge-case.from: ? = # (empty)
+edge-case.from: > = # (empty)
+Var_Parse: ${.TARGET} out of nothing. (eval)
+: Making edge-case.from out of nothing.
+edge-case.to: > = edge-case.additional
+edge-case.to: ? = edge-case.additional
+edge-case.to: > = edge-case.additional edge-case.from
+edge-case.to: ? = edge-case.additional edge-case.from
+Var_Parse: ${.TARGET} from ${.ALLSRC}. (eval)
+Var_Parse: ${.ALLSRC}. (eval)
+: Making edge-case.to from edge-case.additional edge-case.from.
+everything: > = archive-suffix.mk
+everything: ? = archive-suffix.mk
+everything: > = archive-suffix.mk archive.mk
+everything: ? = archive-suffix.mk archive.mk
+Var_Parse: ${.TARGET} from ${.ALLSRC}. (eval)
+Var_Parse: ${.ALLSRC}. (eval)
+: Making everything from archive-suffix.mk archive.mk.
+step1: > = edge-case.to
+step1: ? = edge-case.to
+step1: > = edge-case.to everything
+step1: ? = edge-case.to everything
+.END: @ = .END
+.END: * = .END
+SuffFindDeps ".END"
+ No known suffix on .END. Using .NULL suffix
+adding suffix rules
+.END: @ = .END
+.END: * = .END
+Wildcard expanding ".END"...
+.END: ? = # (empty)
+.END: > = # (empty)
+exit status 0
diff --git a/contrib/bmake/unit-tests/suff.mk b/contrib/bmake/unit-tests/suff.mk
new file mode 100644
index 000000000000..53f6eb82b224
--- /dev/null
+++ b/contrib/bmake/unit-tests/suff.mk
@@ -0,0 +1,41 @@
+# $NetBSD: suff.mk,v 1.3 2025/01/14 21:39:25 rillig Exp $
+#
+# Demonstrate suffix rules and dependency resolution.
+
+
+# Circumvent the file system cache.
+.if !make(init) && !make(step*)
+all:
+ @${MAKE} -f ${MAKEFILE} init
+ @${MAKE} -f ${MAKEFILE} step1
+.endif
+
+
+.if make(init)
+init:
+. if ${.PARSEDIR:tA} != ${.CURDIR:tA}
+${:U}!= cd ${MAKEFILE:H} && cp a*.mk ${.CURDIR}
+. endif
+.endif
+
+
+.if make(step1)
+step1: .PHONY edge-case.to everything
+
+.MAKEFLAGS: -dsv
+
+.SUFFIXES: .from .to
+
+.from.to:
+ : Making ${.TARGET} from ${.ALLSRC}.
+
+# When making this target, ${.ARCHIVE} is undefined, but there's no warning.
+# expect: Var_Parse: ${.ARCHIVE}.additional (eval)
+edge-case.to: ${.PREFIX}${.ARCHIVE}.additional
+
+edge-case.from edge-case.additional:
+ : Making ${.TARGET} out of nothing.
+
+everything: .PHONY a*.mk
+ : Making ${.TARGET} from ${.ALLSRC}.
+.endif
diff --git a/contrib/bmake/unit-tests/unexport.mk b/contrib/bmake/unit-tests/unexport.mk
index 4363aaac3eee..4bcc5b21ca02 100644
--- a/contrib/bmake/unit-tests/unexport.mk
+++ b/contrib/bmake/unit-tests/unexport.mk
@@ -1,4 +1,4 @@
-# $NetBSD: unexport.mk,v 1.5 2020/10/24 08:50:17 rillig Exp $
+# $NetBSD: unexport.mk,v 1.6 2023/10/19 18:24:33 rillig Exp $
# pick up a bunch of exported vars
FILTER_CMD= grep ^UT_
@@ -10,7 +10,7 @@ UT_TEST= unexport
# Until 2020-08-08, Var_UnExport had special handling for '\n', that code
# was not reachable though. At that point, backslash-newline has already
-# been replaced with a simple space, and variables are not yet expanded.
+# been replaced with a simple space, and expressions are not yet expanded.
UT_BEFORE_NL= before
UT_AFTER_NL= after
.export UT_BEFORE_NL UT_AFTER_NL
diff --git a/contrib/bmake/unit-tests/use-inference.exp b/contrib/bmake/unit-tests/use-inference.exp
index 135deabc918e..12ed67354edd 100644
--- a/contrib/bmake/unit-tests/use-inference.exp
+++ b/contrib/bmake/unit-tests/use-inference.exp
@@ -3,5 +3,5 @@ make: don't know how to make use-inference.to (continuing)
`all' not remade because of errors.
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/var-eval-short.exp b/contrib/bmake/unit-tests/var-eval-short.exp
index f574a6444e1b..5f914e9e6c26 100644
--- a/contrib/bmake/unit-tests/var-eval-short.exp
+++ b/contrib/bmake/unit-tests/var-eval-short.exp
@@ -1,29 +1,31 @@
-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}})
+make: var-eval-short.mk:45: In the :@ modifier, the variable name "${FAIL}" must not contain a dollar
+ while parsing "${:Uword:@${FAIL}@expr@}"
+Parsing var-eval-short.mk:158: .if 0 && ${0:?${FAIL}then:${FAIL}else}
CondParser_Eval: 0 && ${0:?${FAIL}then:${FAIL}else}
-Var_Parse: ${0:?${FAIL}then:${FAIL}else} (parse-only)
+Var_Parse: ${0:?${FAIL}then:${FAIL}else} (parse)
Parsing modifier ${0:?...}
+Var_Parse: ${FAIL}then:${FAIL}else} (parse)
Modifier part: "${FAIL}then"
+Var_Parse: ${FAIL}else} (parse)
Modifier part: "${FAIL}else"
-Result of ${0:?${FAIL}then:${FAIL}else} is "" (parse-only, defined)
-Parsing line 163: DEFINED= defined
+Result of ${0:?${FAIL}then:${FAIL}else} is "" (parse, defined)
+Parsing var-eval-short.mk:166: DEFINED= defined
Global: DEFINED = defined
+Parsing var-eval-short.mk:167: .if 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else}
CondParser_Eval: 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else}
-Var_Parse: ${DEFINED:L:?${FAIL}then:${FAIL}else} (parse-only)
+Var_Parse: ${DEFINED:L:?${FAIL}then:${FAIL}else} (parse)
Parsing modifier ${DEFINED:L}
-Result of ${DEFINED:L} is "defined" (parse-only, regular)
+Result of ${DEFINED:L} is "defined" (parse, regular)
Parsing modifier ${DEFINED:?...}
+Var_Parse: ${FAIL}then:${FAIL}else} (parse)
Modifier part: "${FAIL}then"
+Var_Parse: ${FAIL}else} (parse)
Modifier part: "${FAIL}else"
-Result of ${DEFINED:?${FAIL}then:${FAIL}else} is "defined" (parse-only, regular)
-Parsing line 166: .MAKEFLAGS: -d0
+Result of ${DEFINED:?${FAIL}then:${FAIL}else} is "defined" (parse, regular)
+Parsing var-eval-short.mk:169: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
Global: .MAKEFLAGS = -r -k -d cpv -d
Global: .MAKEFLAGS = -r -k -d cpv -d 0
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/var-eval-short.mk b/contrib/bmake/unit-tests/var-eval-short.mk
index a099b6871d1e..d6dc36254851 100644
--- a/contrib/bmake/unit-tests/var-eval-short.mk
+++ b/contrib/bmake/unit-tests/var-eval-short.mk
@@ -1,4 +1,4 @@
-# $NetBSD: var-eval-short.mk,v 1.8 2021/12/27 18:54:19 rillig Exp $
+# $NetBSD: var-eval-short.mk,v 1.17 2025/01/11 20:54:45 rillig Exp $
#
# Tests for each variable modifier to ensure that they only do the minimum
# necessary computations. If the result of the expression is irrelevant,
@@ -18,7 +18,7 @@ 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.
+# TODO: Test the modifiers in the same order as they occur in ApplyModifier.
.if 0 && ${FAIL}
.endif
@@ -41,6 +41,7 @@ FAIL= ${:!echo unexpected 1>&2!}
# after the loop, when undefining the temporary global loop variable.
# Since var.c 1.907 from 2021-04-04, a '$' is no longer allowed in the
# variable name.
+# expect+1: In the :@ modifier, the variable name "${FAIL}" must not contain a dollar
.if 0 && ${:Uword:@${FAIL}@expr@}
.endif
@@ -79,8 +80,9 @@ DEFINED= # defined
.if 0 && ${:Uword:E}
.endif
-# As of 2021-03-14, the error 'Invalid time value: ${FAIL}}' is ok since
-# ':gmtime' does not expand its argument.
+# Before var.c 1.1050 from 2023-05-09, the ':gmtime' modifier produced the
+# error message 'Invalid time value: ${FAIL}}' since it did not expand its
+# argument.
.if 0 && ${:Uword:gmtime=${FAIL}}
.endif
@@ -93,8 +95,9 @@ DEFINED= # defined
.if 0 && ${value:L}
.endif
-# As of 2021-03-14, the error 'Invalid time value: ${FAIL}}' is ok since
-# ':localtime' does not expand its argument.
+# Before var.c 1.1050 from 2023-05-09, the ':localtime' modifier produced the
+# error message 'Invalid time value: ${FAIL}}' since it did not expand its
+# argument.
.if 0 && ${:Uword:localtime=${FAIL}}
.endif
diff --git a/contrib/bmake/unit-tests/var-op-append.mk b/contrib/bmake/unit-tests/var-op-append.mk
index 420ee376b75d..e16b89139cc1 100644
--- a/contrib/bmake/unit-tests/var-op-append.mk
+++ b/contrib/bmake/unit-tests/var-op-append.mk
@@ -1,7 +1,20 @@
-# $NetBSD: var-op-append.mk,v 1.9 2021/04/04 10:13:09 rillig Exp $
+# $NetBSD: var-op-append.mk,v 1.12 2023/11/02 05:46:26 rillig Exp $
#
-# Tests for the += variable assignment operator, which appends to a variable,
-# creating it if necessary.
+# Tests for the '+=' variable assignment operator, which appends to a
+# variable, creating it if necessary.
+#
+# See also
+# var-op.mk
+#
+# Standards
+# The '+=' variable assignment operator is planned to be added in
+# POSIX.1-202x.
+#
+# This implementation does not support the immediate-expansion macros
+# specified in POSIX.1-202x. All variables are delayed-expansion.
+#
+# History
+# The '+=' variable assignment operator was added before 1993-03-21.
# Appending to an undefined variable is possible.
# The variable is created, and no extra space is added before the value.
@@ -26,7 +39,7 @@ VAR+= # empty
# '+=' assignment operator. As far as possible, the '+' is interpreted as
# part of the assignment operator.
#
-# See Parse_Var
+# See Parse_Var, AdjustVarassignOp.
C++= value
.if ${C+} != "value" || defined(C++)
. error
@@ -43,4 +56,33 @@ VAR.${:U\$\$\$\$\$\$\$\$}+= dollars
. error
.endif
+
+# Appending to an environment variable in the global scope creates a global
+# variable of the same name, taking its initial value from the environment
+# variable. After the assignment, the environment variable is left as-is,
+# the value of the global variable is not synced back to the environment
+# variable.
+export ENV_PLUS_GLOBAL=from-env-value
+ENV_PLUS_GLOBAL+= appended-value
+.if ${ENV_PLUS_GLOBAL} != "from-env-value appended-value"
+. error
+.endif
+EXPORTED!= echo "$$ENV_PLUS_GLOBAL"
+.if ${EXPORTED} != "from-env-value"
+. error
+.endif
+
+# Appending to an environment variable in the command line scope ignores the
+# environment variable.
+export ENV_PLUS_COMMAND=from-env-value
+.MAKEFLAGS: ENV_PLUS_COMMAND+=appended-command
+.if ${ENV_PLUS_COMMAND} != "appended-command"
+. error ${ENV_PLUS_COMMAND}
+.endif
+EXPORTED!= echo "$$ENV_PLUS_GLOBAL"
+.if ${EXPORTED} != "from-env-value"
+. error
+.endif
+
+
all:
diff --git a/contrib/bmake/unit-tests/var-op-assign.exp b/contrib/bmake/unit-tests/var-op-assign.exp
index 73e580403d78..83459de4184d 100644
--- a/contrib/bmake/unit-tests/var-op-assign.exp
+++ b/contrib/bmake/unit-tests/var-op-assign.exp
@@ -1,6 +1,6 @@
this will be evaluated later
-make: "var-op-assign.mk" line 59: Invalid line type
-make: "var-op-assign.mk" line 93: Parsing still continues until here.
+make: var-op-assign.mk:60: Invalid line "VARIABLE NAME= variable value"
+make: var-op-assign.mk:95: Parsing still continues until here.
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/var-op-assign.mk b/contrib/bmake/unit-tests/var-op-assign.mk
index 18ecf8d0d5ed..a218dbfdac0a 100644
--- a/contrib/bmake/unit-tests/var-op-assign.mk
+++ b/contrib/bmake/unit-tests/var-op-assign.mk
@@ -1,4 +1,4 @@
-# $NetBSD: var-op-assign.mk,v 1.8 2021/03/15 19:15:04 rillig Exp $
+# $NetBSD: var-op-assign.mk,v 1.12 2025/06/28 22:39:29 rillig Exp $
#
# Tests for the = variable assignment operator, which overwrites an existing
# variable or creates it.
@@ -56,6 +56,7 @@ VAR= ${:! echo 'this will be evaluated later' 1>&2 !}
# In a variable assignment, the variable name must consist of a single word.
# The following line therefore generates a parse error.
+# expect+1: Invalid line "VARIABLE NAME= variable value"
VARIABLE NAME= variable value
# But if the whitespace appears inside parentheses or braces, everything is
@@ -65,7 +66,7 @@ VARIABLE NAME= variable value
# neither contain parentheses nor braces. This is only a side-effect from
# the implementation of the parser, which cheats when parsing a variable
# name. It only counts parentheses and braces instead of properly parsing
-# nested variable expressions such as VAR.${param}.
+# nested expressions such as VAR.${param}.
#
VAR(spaces in parentheses)= ()
VAR{spaces in braces}= {}
@@ -90,6 +91,7 @@ VARNAME_BRACES= VAR{spaces in braces}
# unexpected variable values.
#
# Therefore, just output an info message.
+# expect+1: Parsing still continues until here.
.info Parsing still continues until here.
all:
diff --git a/contrib/bmake/unit-tests/var-op-default.mk b/contrib/bmake/unit-tests/var-op-default.mk
index ca4fbcc27c88..9d07ddf39e41 100644
--- a/contrib/bmake/unit-tests/var-op-default.mk
+++ b/contrib/bmake/unit-tests/var-op-default.mk
@@ -1,4 +1,4 @@
-# $NetBSD: var-op-default.mk,v 1.3 2020/12/07 21:35:43 rillig Exp $
+# $NetBSD: var-op-default.mk,v 1.5 2023/11/19 22:32:44 rillig Exp $
#
# Tests for the ?= variable assignment operator, which only assigns
# if the variable is still undefined.
@@ -45,7 +45,7 @@ i?= default
# and 'VAR.${param}' expand to 'VAR.param', and the second '?=' assignment
# has no effect.
#
-# Since 2000.05.11.07.43.42 it has been possible to use nested variable
+# Since 2000.05.11.07.43.42 it has been possible to use nested
# expressions in variable names, which made make much more versatile.
# On 2008.03.31.00.12.21, this particular case of the '?=' operator has been
# fixed. Before, the '?=' operator had not expanded the variable name
@@ -61,8 +61,8 @@ VAR.${:Uparam}?= not used
# Now demonstrate that the variable name is indeed expanded exactly once.
# This is tricky to measure correctly since there are many inconsistencies
-# in and around the code that expands variable expressions in the various
-# places where variable expressions can occur. If in doubt, enable the
+# in and around the code that expands expressions in the various
+# places where expressions can occur. If in doubt, enable the
# following debug flags to see what happens:
#.MAKEFLAGS: -dcpv
EXPAND_NAME= EXPAND.$$$$ # The full variable name is EXPAND.$$
diff --git a/contrib/bmake/unit-tests/var-op-expand.exp b/contrib/bmake/unit-tests/var-op-expand.exp
index a4ba53942cf7..5e2c3d1936d7 100644
--- a/contrib/bmake/unit-tests/var-op-expand.exp
+++ b/contrib/bmake/unit-tests/var-op-expand.exp
@@ -1,7 +1,21 @@
-make: "var-op-expand.mk" line 265: Unknown modifier "s,value,replaced,"
-make: "var-op-expand.mk" line 268: warning: XXX Neither branch should be taken.
-make: "var-op-expand.mk" line 273: Unknown modifier "s,value,replaced,"
-make: "var-op-expand.mk" line 274: warning: XXX Neither branch should be taken.
+make: var-op-expand.mk:274: Unknown modifier ":s,value,replaced,"
+ while evaluating variable "later" with value ""
+ while evaluating variable "indirect" with value "${later:s,value,replaced,} ok ${later:value=sysv}"
+make: var-op-expand.mk:282: Unknown modifier ":s,value,replaced,"
+ while evaluating variable "later" with value "lowercase-value"
+ while evaluating variable "indirect" with value "${later:s,value,replaced,} ok ${later:value=sysv}"
+make: var-op-expand.mk:295: Bad condition
+ while evaluating condition " < 0 "
+make: var-op-expand.mk:295: Unknown modifier ":Z1"
+ while parsing "${:Z1}:${:Z2}}"
+ while evaluating then-branch of condition " < 0 "
+make: var-op-expand.mk:295: Unknown modifier ":Z2"
+ while parsing "${:Z2}}"
+ while evaluating else-branch of condition " < 0 "
+make: var-op-expand.mk:295: Unknown modifier ":Z1"
+ while evaluating "${:Z1}:${:Z2}}" with value ""
+make: var-op-expand.mk:295: Unknown modifier ":Z2"
+ while evaluating "${:Z2}}" with value ""
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/var-op-expand.mk b/contrib/bmake/unit-tests/var-op-expand.mk
index 1d905aeb3757..fb9e1713438b 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.16 2021/12/28 10:47:00 rillig Exp $
+# $NetBSD: var-op-expand.mk,v 1.25 2025/06/29 11:27:21 rillig Exp $
#
# Tests for the := variable assignment operator, which expands its
# right-hand side.
@@ -20,9 +20,9 @@ VAR:= value
# When a ':=' assignment is performed, its right-hand side is evaluated and
# expanded as far as possible. Contrary to other situations, '$$' and
-# variable expressions based on undefined variables are preserved though.
+# expressions based on undefined variables are preserved though.
#
-# Whether a variable expression is undefined or not is determined at the end
+# Whether an expression is undefined or not is determined at the end
# of evaluating the expression. The consequence is that ${:Ufallback} expands
# to "fallback"; initially this expression is undefined since it is based on
# the variable named "", which is guaranteed to be never defined, but at the
@@ -37,7 +37,7 @@ VAR:= $$ $$$$ $$$$$$$$
.endif
-# reference to a variable containing a literal dollar sign
+# reference to a variable containing literal dollar signs
REF= $$ $$$$ $$$$$$$$
VAR:= ${REF}
REF= too late
@@ -49,6 +49,9 @@ REF= too late
# reference to an undefined variable
.undef UNDEF
VAR:= <${UNDEF}>
+.if ${VAR} != "<>"
+. error
+.endif
UNDEF= after
.if ${VAR} != "<after>"
. error
@@ -68,6 +71,9 @@ REF= too late
# expression with an indirect modifier referring to an undefined variable
.undef UNDEF
VAR:= ${:${UNDEF}}
+.if ${VAR} != ""
+. error
+.endif
UNDEF= Uwas undefined
.if ${VAR} != "was undefined"
. error
@@ -99,6 +105,9 @@ UNDEF= Uwas undefined
REF2= <${REF3}>
REF= ${REF2}
VAR:= ${REF}
+.if ${VAR} != "<>"
+. error
+.endif
REF3= too late
.if ${VAR} != "<too late>"
. error
@@ -261,21 +270,26 @@ later= lowercase-value
.undef later
INDIRECT:= ${LATER:S,value,replaced,} OK ${LATER:value=sysv}
indirect:= ${INDIRECT:tl}
-# expect+1: Unknown modifier "s,value,replaced,"
+# expect+1: Unknown modifier ":s,value,replaced,"
.if ${indirect} != " ok "
. error
.else
-. warning XXX Neither branch should be taken.
+. error
.endif
LATER= uppercase-value
later= lowercase-value
-# expect+1: Unknown modifier "s,value,replaced,"
+# expect+1: Unknown modifier ":s,value,replaced,"
.if ${indirect} != "uppercase-replaced ok uppercase-sysv"
-. warning XXX Neither branch should be taken.
+. error
.else
. error
.endif
-all:
- @:;
+# FIXME: The expression is evaluated twice, for no obvious reason.
+# expect+5: Bad condition
+# expect+4: Unknown modifier ":Z1"
+# expect+3: Unknown modifier ":Z2"
+# expect+2: Unknown modifier ":Z1"
+# expect+1: Unknown modifier ":Z2"
+_:= ${ < 0 :?${:Z1}:${:Z2}}
diff --git a/contrib/bmake/unit-tests/var-op-shell.exp b/contrib/bmake/unit-tests/var-op-shell.exp
index 0e9bd2cbc35a..0a44ac3fb938 100644
--- a/contrib/bmake/unit-tests/var-op-shell.exp
+++ b/contrib/bmake/unit-tests/var-op-shell.exp
@@ -1,11 +1,17 @@
-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
+make: var-op-shell.mk:32: warning: Command "echo "failed"; (exit 13)" exited with status 13
+make: var-op-shell.mk:39: warning: Command "exit 13" exited with status 13
+make: var-op-shell.mk:62: warning: "kill $$" exited on a signal
/bin/no/such/command: not found
-make: "var-op-shell.mk" line 65: warning: "/bin/no/such/command" returned non-zero status
+make: var-op-shell.mk:69: warning: Command "/bin/no/such/command" exited with status 127
stderr
Capturing the output of command "echo '$$$$'"
Global: OUTPUT = $$$$
Global: .MAKEFLAGS = -r -k -d v -d
Global: .MAKEFLAGS = -r -k -d v -d 0
+Var_Parse: ${UNDEF}y (eval)
+Capturing the output of command "echo xy"
+Global: OUTPUT_OF_UNDEF = xy
+Var_Parse: ${OUTPUT_OF_UNDEF} != "xy" (eval-defined-loud)
+Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d
+Global: .MAKEFLAGS = -r -k -d v -d 0 -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 bd2a48f17cc4..f9c7c717b8f1 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.6 2022/01/10 20:32:29 rillig Exp $
+# $NetBSD: var-op-shell.mk,v 1.11 2025/01/11 21:21:33 rillig Exp $
#
# Tests for the != variable assignment operator, which runs its right-hand
# side through the shell.
@@ -28,13 +28,15 @@ OUTPUT!= true
# '::!=', 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
+# expect+1: warning: Command "echo "failed"; (exit 13)" exited with status 13
+OUTPUT!= echo "failed"; (exit 13)
.if ${OUTPUT} != "failed"
. error
.endif
# A command with empty output may fail as well.
-OUTPUT!= false
+# expect+1: warning: Command "exit 13" exited with status 13
+OUTPUT!= exit 13
.if ${OUTPUT} != ""
. error
.endif
@@ -48,7 +50,7 @@ OUTPUT!= echo "line 1"; echo "line 2"
# A failing command in the middle results in the exit status 0, which in the
# end means that the whole sequence of commands succeeded.
-OUTPUT!= echo "before"; false; echo "after"
+OUTPUT!= echo "before"; (exit 13); echo "after"
.if ${OUTPUT} != "before after"
. error
.endif
@@ -56,12 +58,14 @@ OUTPUT!= echo "before"; false; echo "after"
# This should result in a warning about "exited on a signal".
# This used to be kill -14 (SIGALRM), but that stopped working on
# Darwin18 after recent update.
+# expect+1: warning: "kill $$" exited on a signal
OUTPUT!= kill $$$$
.if ${OUTPUT} != ""
. error
.endif
# A nonexistent command produces a non-zero exit status.
+# expect+1: warning: Command "/bin/no/such/command" exited with status 127
OUTPUT!= /bin/no/such/command
.if ${OUTPUT} != ""
. error
@@ -87,4 +91,31 @@ OUTPUT!= echo '$$$$$$$$'
OUTPUT!= echo '$$$$$$$$'
.MAKEFLAGS: -d0
+
+# Since main.c 1.607 from 2024-01-05, long shell commands are not run directly
+# via '$shell -c $command', they are first written to a temporary file that is
+# then fed to the shell via '$shell $tmpfile'.
+OUTPUT_SHORT!= echo "$$0"
+OUTPUT_LONG!= echo "$$0" || : ${:U:range=1000}
+# When running '$shell -c $command', '$0' in the shell evaluates to the name
+# of the shell.
+.if ${OUTPUT_SHORT:T} != ${.SHELL:T}
+. error
+.endif
+# When running '$shell $tmpfile', '$0' in the shell evaluates to the name of
+# the temporary file.
+.if !${OUTPUT_LONG:M*/make*}
+. error
+.endif
+
+
+# An undefined expression results in an empty string.
+.MAKEFLAGS: -dv
+OUTPUT_OF_UNDEF!= echo x${UNDEF}y
+.if ${OUTPUT_OF_UNDEF} != "xy"
+. error
+.endif
+.MAKEFLAGS: -d0
+
+
all:
diff --git a/contrib/bmake/unit-tests/var-readonly.exp b/contrib/bmake/unit-tests/var-readonly.exp
new file mode 100644
index 000000000000..ae266753ee71
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-readonly.exp
@@ -0,0 +1,4 @@
+Global: ignoring delete 'N' as it is read-only
+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-readonly.mk b/contrib/bmake/unit-tests/var-readonly.mk
new file mode 100644
index 000000000000..e9ff6f38819f
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-readonly.mk
@@ -0,0 +1,27 @@
+# $NetBSD: var-readonly.mk,v 1.4 2023/12/20 08:42:10 rillig Exp $
+
+# the answer
+N = 42
+.READONLY: N
+# this should be ignored
+N = 666
+.if ${N} != 42
+.error N ($N) should be 42
+.endif
+
+# undef should fail
+.MAKEFLAGS: -dv
+.undef N
+.ifndef N
+.error N should not be undef'd
+.endif
+.MAKEFLAGS: -d0
+
+.NOREADONLY: N
+# now we can change it
+N = 69
+.if ${N} == 42
+.error N should not be 42
+.endif
+
+all:
diff --git a/contrib/bmake/unit-tests/var-recursive.exp b/contrib/bmake/unit-tests/var-recursive.exp
index 44c381f94ff9..97568873bf1b 100644
--- a/contrib/bmake/unit-tests/var-recursive.exp
+++ b/contrib/bmake/unit-tests/var-recursive.exp
@@ -1,19 +1,29 @@
-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
+make: var-recursive.mk:11: Variable DIRECT is recursive.
+ while evaluating variable "DIRECT" with value "${DIRECT}"
+ in make[1] in directory "<curdir>"
+make: var-recursive.mk:11: <>
+make: var-recursive.mk:19: Variable INDIRECT1 is recursive.
+ while evaluating variable "INDIRECT2" with value "${INDIRECT1}"
+ while evaluating variable "INDIRECT1" with value "${INDIRECT2}"
+ in make[1] in directory "<curdir>"
+make: var-recursive.mk:19: <>
+make: var-recursive.mk:26: <ok>
+make: var-recursive.mk:34: Variable MODIFIERS is recursive.
+ while evaluating variable "MODIFIERS" with value "${MODIFIERS:Mpattern}"
+ in make[1] in directory "<curdir>"
+make: var-recursive.mk:34: <Mpattern}>
+make: var-recursive.mk:43: Variable V is recursive.
+ while evaluating variable "V" with value "$V"
+ in make[1] in directory "<curdir>"
+make: var-recursive.mk:43: <>
+make: Fatal errors encountered -- cannot continue
+make: stopped making "loadtime" in unit-tests
+sub-exit status 1
+: before-recursive
+make: Variable VAR is recursive.
+ while evaluating variable "VAR" with value "${VAR}"
+ in command ": recursive-line-before <${VAR}> recursive-line-after"
+ in target "runtime"
+ in make[1] in directory "<curdir>"
+sub-exit status 2
exit status 0
diff --git a/contrib/bmake/unit-tests/var-recursive.mk b/contrib/bmake/unit-tests/var-recursive.mk
index 1825c8a63120..b1c183e6f1b1 100644
--- a/contrib/bmake/unit-tests/var-recursive.mk
+++ b/contrib/bmake/unit-tests/var-recursive.mk
@@ -1,61 +1,64 @@
-# $NetBSD: var-recursive.mk,v 1.4 2022/01/29 10:21:26 rillig Exp $
+# $NetBSD: var-recursive.mk,v 1.12 2025/04/13 09:29:33 rillig Exp $
#
-# Tests for variable expressions that refer to themselves and thus
-# cannot be evaluated.
+# Tests for expressions that refer to themselves and thus cannot be
+# evaluated, as that would lead to an endless loop.
-TESTS= direct indirect conditional short target
+.if make(loadtime)
-# Since make exits immediately when it detects a recursive expression,
-# the actual tests are run in sub-makes.
-TEST?= # none
-.if ${TEST} == ""
-all:
-.for test in ${TESTS}
- @${.MAKE} -f ${MAKEFILE} TEST=${test} || :
-.endfor
-
-.elif ${TEST} == direct
+DIRECT= ${DIRECT} # Defining a recursive variable is not an error.
+# expect+2: Variable DIRECT is recursive.
+# expect+1: <>
+. info <${DIRECT}> # But expanding such a variable is an error.
-DIRECT= ${DIRECT} # Defining a recursive variable is not yet an error.
-. info still there # Therefore this line is printed.
-. info ${DIRECT} # But expanding the variable is an error.
-
-.elif ${TEST} == indirect
# The chain of variables that refer to each other may be long.
INDIRECT1= ${INDIRECT2}
INDIRECT2= ${INDIRECT1}
-. info ${INDIRECT1}
+# expect+2: Variable INDIRECT1 is recursive.
+# expect+1: <>
+. info <${INDIRECT1}>
-.elif ${TEST} == conditional
# The variable refers to itself, but only in the branch of a condition that
-# is never satisfied and is thus not evaluated.
+# is not satisfied and is thus not evaluated.
CONDITIONAL= ${1:?ok:${CONDITIONAL}}
-. info ${CONDITIONAL}
+# expect+1: <ok>
+. info <${CONDITIONAL}>
+
+
+# An expression with modifiers is skipped halfway. This can lead to wrong
+# follow-up error messages, but recursive variables occur seldom.
+MODIFIERS= ${MODIFIERS:Mpattern}
+# expect+2: Variable MODIFIERS is recursive.
+# expect+1: <Mpattern}>
+. info <${MODIFIERS}>
-.elif ${TEST} == short
# Short variable names can be expanded using the short-hand $V notation,
# which takes a different code path in Var_Parse for parsing the variable
# name. Ensure that these are checked as well.
V= $V
-. info $V
+# expect+2: Variable V is recursive.
+# expect+1: <>
+. info <$V>
-.elif ${TEST} == target
+.elif make(runtime)
-# 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
+runtime:
+# expect: : before-recursive
+ : before-recursive
+# expect: make: Variable VAR is recursive.
+# expect-not-matches: ^: recursive%-line%-before
+# expect-not-matches: ^: recursive%-line%-after
+ : recursive-line-before <${VAR}> recursive-line-after
+# expect-not-matches: ^: after%-recursive
+ : after-recursive
.else
-. error Unknown test "${TEST}"
-.endif
all:
+ @${MAKE} -f ${MAKEFILE} loadtime || echo "sub-exit status $$?"
+ @${MAKE} -f ${MAKEFILE} runtime || echo "sub-exit status $$?"
+
+.endif
diff --git a/contrib/bmake/unit-tests/var-scope-cmdline.exp b/contrib/bmake/unit-tests/var-scope-cmdline.exp
index a1227a1dd1f2..41291d79a2fb 100644
--- a/contrib/bmake/unit-tests/var-scope-cmdline.exp
+++ b/contrib/bmake/unit-tests/var-scope-cmdline.exp
@@ -1,4 +1,4 @@
-make: "var-scope-cmdline.mk" line 67: global
-make: "var-scope-cmdline.mk" line 76: makeflags
+make: var-scope-cmdline.mk:72: global
+make: var-scope-cmdline.mk:82: makeflags
makeflags
exit status 0
diff --git a/contrib/bmake/unit-tests/var-scope-cmdline.mk b/contrib/bmake/unit-tests/var-scope-cmdline.mk
index 1f4a3e700253..5c0f246a0a22 100644
--- a/contrib/bmake/unit-tests/var-scope-cmdline.mk
+++ b/contrib/bmake/unit-tests/var-scope-cmdline.mk
@@ -1,4 +1,4 @@
-# $NetBSD: var-scope-cmdline.mk,v 1.1 2022/01/23 16:25:54 rillig Exp $
+# $NetBSD: var-scope-cmdline.mk,v 1.4 2023/11/19 21:47:52 rillig Exp $
#
# Tests for variables specified on the command line.
#
@@ -55,15 +55,20 @@
# temporary loop variable after finishing the loop. It was probably not
# intended back then that a side effect of this seemingly simple change was
# that both global and cmdline variables could now be undefined at will as a
-# side effect of evaluating a variable expression. As of 2021-02-23, this is
+# side effect of evaluating an expression. As of 2021-02-23, this is
# still possible.
#
# Most cmdline variables are set at the very beginning, when parsing the
# command line arguments. Using the special target '.MAKEFLAGS', it is
# possible to set cmdline variables at any later time.
+#
+# See also:
+# varcmd.mk
+# varname-makeflags.mk
# A normal global variable, without any cmdline variable nearby.
VAR= global
+# expect+1: global
.info ${VAR}
# The global variable is "overridden" by simply deleting it and then
@@ -73,6 +78,7 @@ VAR= global
#
# See varmod-loop.mk for a non-obvious way to undefine a cmdline variable.
.MAKEFLAGS: VAR=makeflags
+# expect+1: makeflags
.info ${VAR}
# If Var_SetWithFlags should ever forget to delete the global variable,
diff --git a/contrib/bmake/unit-tests/var-scope-local-legacy.exp b/contrib/bmake/unit-tests/var-scope-local-legacy.exp
index 39a9383953dd..33ce145fb8fd 100644
--- a/contrib/bmake/unit-tests/var-scope-local-legacy.exp
+++ b/contrib/bmake/unit-tests/var-scope-local-legacy.exp
@@ -1 +1,6 @@
+: LEN4=undef_
+: XY=undef_
+: AF=undef_
+: %D=undef_ %F=undef_
+: @D=global-value_ @F=all_
exit status 0
diff --git a/contrib/bmake/unit-tests/var-scope-local-legacy.mk b/contrib/bmake/unit-tests/var-scope-local-legacy.mk
index e519d63e7c51..70bc20fd9848 100644
--- a/contrib/bmake/unit-tests/var-scope-local-legacy.mk
+++ b/contrib/bmake/unit-tests/var-scope-local-legacy.mk
@@ -1,8 +1,35 @@
-# $NetBSD: var-scope-local-legacy.mk,v 1.1 2022/01/23 16:25:54 rillig Exp $
+# $NetBSD: var-scope-local-legacy.mk,v 1.3 2023/12/17 14:07:22 rillig Exp $
#
# Tests for legacy target-local variables, such as ${<F} or ${@D}.
-# TODO: Implementation
-all:
- @:;
+# In the global or command line scopes, the legacy forms are not recognized,
+# as the target-specific variables are not available either. The expressions
+# are retained so that they can be resolved later, in the target scope.
+.if "${@D}" != "\${@D}"
+. error
+.endif
+
+# It's possible to define variables of the legacy name in the global or
+# command line scope, and they override the target-local variables, leading to
+# unnecessary confusion.
+@D= global-value
+.if "${@D}" != "global-value"
+. error
+.endif
+
+
+all: .PHONY
+ # Only variables of length 2 can be legacy, this one cannot.
+ : LEN4=${LEN4:Uundef}_
+ # The second character of the name must be 'D' or 'F'.
+ : XY=${XY:Uundef}_
+ # The first character must name one of the 7 predefined local
+ # variables, 'A' is not such a character.
+ : AF=${AF:Uundef}_
+ # The variable '.MEMBER' is undefined, therefore '%D' and '%F' are
+ # undefined as well.
+ : %D=${%D:Uundef}_ %F=${%F:Uundef}_
+ # The directory name of the target is shadowed by the global variable,
+ # it would be '.' otherwise. The basename is 'all'.
+ : @D=${@D:Uundef}_ @F=${@F:Uundef}_
diff --git a/contrib/bmake/unit-tests/var-scope-local.exp b/contrib/bmake/unit-tests/var-scope-local.exp
index 403bf83884f7..eddf5985a0ed 100644
--- a/contrib/bmake/unit-tests/var-scope-local.exp
+++ b/contrib/bmake/unit-tests/var-scope-local.exp
@@ -1,21 +1,71 @@
-Global: .ALLTARGETS = one
-Global: .ALLTARGETS = one two
+Global: .ALLTARGETS = all target-rule.ext dir/subdir/target-rule.ext target-rule.ir-gen-from dir/subdir/target-rule-dir.ir-gen-from inference-rule.ir-to dir/subdir/inference-rule.ir-to inference-rule.ir-from dir/subdir/inference-rule.ir-from inference-rule-chain.ir-to dir/subdir/inference-rule-chain.ir-to inference-rule-chain.ir-gen-from dir/subdir/inference-rule-chain.ir-gen-from one
+Global: .ALLTARGETS = all target-rule.ext dir/subdir/target-rule.ext target-rule.ir-gen-from dir/subdir/target-rule-dir.ir-gen-from inference-rule.ir-to dir/subdir/inference-rule.ir-to inference-rule.ir-from dir/subdir/inference-rule.ir-from inference-rule-chain.ir-to dir/subdir/inference-rule-chain.ir-to inference-rule-chain.ir-gen-from dir/subdir/inference-rule-chain.ir-gen-from 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
+one: ignoring ' = three' as the variable name '' expands to empty
+two: ignoring ' = three' as the variable name '' expands to empty
Global: one two = # (empty)
Global: one two = three
Global: .MAKEFLAGS = -r -k -d v -d
Global: .MAKEFLAGS = -r -k -d v -d 0
+target-rule.ext: @ = <target-rule.ext>
+target-rule.ext: % = <undefined>
+target-rule.ext: ? = <>
+target-rule.ext: < = <undefined>
+target-rule.ext: * = <target-rule.ext>
+dir/subdir/target-rule.ext: @ = <dir/subdir/target-rule.ext>
+dir/subdir/target-rule.ext: % = <undefined>
+dir/subdir/target-rule.ext: ? = <>
+dir/subdir/target-rule.ext: < = <undefined>
+dir/subdir/target-rule.ext: * = <dir/subdir/target-rule.ext>
+target-rule.ir-gen-from: @ = <target-rule.ir-gen-from>
+target-rule.ir-gen-from: % = <undefined>
+target-rule.ir-gen-from: ? = <>
+target-rule.ir-gen-from: < = <undefined>
+target-rule.ir-gen-from: * = <target-rule>
+dir/subdir/target-rule-dir.ir-gen-from: @ = <dir/subdir/target-rule-dir.ir-gen-from>
+dir/subdir/target-rule-dir.ir-gen-from: % = <undefined>
+dir/subdir/target-rule-dir.ir-gen-from: ? = <>
+dir/subdir/target-rule-dir.ir-gen-from: < = <undefined>
+dir/subdir/target-rule-dir.ir-gen-from: * = <dir/subdir/target-rule-dir>
+inference-rule.ir-to: @ = <inference-rule.ir-to>
+inference-rule.ir-to: % = <undefined>
+inference-rule.ir-to: ? = <inference-rule.ir-from>
+inference-rule.ir-to: < = <inference-rule.ir-from>
+inference-rule.ir-to: * = <inference-rule>
+dir/subdir/inference-rule.ir-to: @ = <dir/subdir/inference-rule.ir-to>
+dir/subdir/inference-rule.ir-to: % = <undefined>
+dir/subdir/inference-rule.ir-to: ? = <dir/subdir/inference-rule.ir-from>
+dir/subdir/inference-rule.ir-to: < = <dir/subdir/inference-rule.ir-from>
+dir/subdir/inference-rule.ir-to: * = <dir/subdir/inference-rule>
+inference-rule-chain.ir-from: @ = <inference-rule-chain.ir-from>
+inference-rule-chain.ir-from: % = <undefined>
+inference-rule-chain.ir-from: ? = <inference-rule-chain.ir-gen-from>
+inference-rule-chain.ir-from: < = <inference-rule-chain.ir-gen-from>
+inference-rule-chain.ir-from: * = <inference-rule-chain>
+inference-rule-chain.ir-to: @ = <inference-rule-chain.ir-to>
+inference-rule-chain.ir-to: % = <undefined>
+inference-rule-chain.ir-to: ? = <inference-rule-chain.ir-from>
+inference-rule-chain.ir-to: < = <inference-rule-chain.ir-from>
+inference-rule-chain.ir-to: * = <inference-rule-chain>
+dir/subdir/inference-rule-chain.ir-from: @ = <dir/subdir/inference-rule-chain.ir-from>
+dir/subdir/inference-rule-chain.ir-from: % = <undefined>
+dir/subdir/inference-rule-chain.ir-from: ? = <dir/subdir/inference-rule-chain.ir-gen-from>
+dir/subdir/inference-rule-chain.ir-from: < = <dir/subdir/inference-rule-chain.ir-gen-from>
+dir/subdir/inference-rule-chain.ir-from: * = <dir/subdir/inference-rule-chain>
+dir/subdir/inference-rule-chain.ir-to: @ = <dir/subdir/inference-rule-chain.ir-to>
+dir/subdir/inference-rule-chain.ir-to: % = <undefined>
+dir/subdir/inference-rule-chain.ir-to: ? = <dir/subdir/inference-rule-chain.ir-from>
+dir/subdir/inference-rule-chain.ir-to: < = <dir/subdir/inference-rule-chain.ir-from>
+dir/subdir/inference-rule-chain.ir-to: * = <dir/subdir/inference-rule-chain>
: 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"
+Making var-scope-local-assign.o with make 'local' and env 'local'.
+Making var-scope-local-append.o with make 'local to var-scope-local-append.o' and env 'local to var-scope-local-append.o'.
+Making var-scope-local-append-global.o with make 'global+local' and env 'global+local'.
+Making var-scope-local-default.o with make 'global' and env 'global'.
+Making var-scope-local-subst.o with make 'global+local' and env 'global+local'.
+Making var-scope-local-shell.o with make 'output' and env 'output'.
+Making .USE var-scope-local-use.o with make 'global' and env '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
index ed1362444504..7a031373e7da 100644
--- a/contrib/bmake/unit-tests/var-scope-local.mk
+++ b/contrib/bmake/unit-tests/var-scope-local.mk
@@ -1,4 +1,4 @@
-# $NetBSD: var-scope-local.mk,v 1.5 2022/02/09 21:09:24 rillig Exp $
+# $NetBSD: var-scope-local.mk,v 1.11 2024/03/05 23:07:58 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
@@ -12,6 +12,64 @@
.MAIN: all
+# Target-local variables in a target rule
+#
+# In target rules, '$*' only strips the extension off the pathname if the
+# extension is listed in '.SUFFIXES'.
+#
+# expect: target-rule.ext: * = <target-rule.ext>
+all: target-rule.ext dir/subdir/target-rule.ext
+target-rule.ext dir/subdir/target-rule.ext: .PHONY
+ @echo '$@: @ = <${@:Uundefined}>'
+ @echo '$@: % = <${%:Uundefined}>'
+ @echo '$@: ? = <${?:Uundefined}>'
+ @echo '$@: < = <${<:Uundefined}>'
+ @echo '$@: * = <${*:Uundefined}>'
+
+.SUFFIXES: .ir-gen-from .ir-from .ir-to
+
+# In target rules, '$*' strips the extension off the pathname of the target
+# if the extension is listed in '.SUFFIXES'.
+#
+# expect: target-rule.ir-gen-from: * = <target-rule>
+all: target-rule.ir-gen-from dir/subdir/target-rule-dir.ir-gen-from
+target-rule.ir-gen-from dir/subdir/target-rule-dir.ir-gen-from:
+ @echo '$@: @ = <${@:Uundefined}>'
+ @echo '$@: % = <${%:Uundefined}>'
+ @echo '$@: ? = <${?:Uundefined}>'
+ @echo '$@: < = <${<:Uundefined}>'
+ @echo '$@: * = <${*:Uundefined}>'
+
+.ir-from.ir-to:
+ @echo '$@: @ = <${@:Uundefined}>'
+ @echo '$@: % = <${%:Uundefined}>'
+ @echo '$@: ? = <${?:Uundefined}>'
+ @echo '$@: < = <${<:Uundefined}>'
+ @echo '$@: * = <${*:Uundefined}>'
+.ir-gen-from.ir-from:
+ @echo '$@: @ = <${@:Uundefined}>'
+ @echo '$@: % = <${%:Uundefined}>'
+ @echo '$@: ? = <${?:Uundefined}>'
+ @echo '$@: < = <${<:Uundefined}>'
+ @echo '$@: * = <${*:Uundefined}>'
+
+# Target-local variables in an inference rule
+all: inference-rule.ir-to dir/subdir/inference-rule.ir-to
+inference-rule.ir-from: .PHONY
+dir/subdir/inference-rule.ir-from: .PHONY
+
+# Target-local variables in a chain of inference rules
+all: inference-rule-chain.ir-to dir/subdir/inference-rule-chain.ir-to
+inference-rule-chain.ir-gen-from: .PHONY
+dir/subdir/inference-rule-chain.ir-gen-from: .PHONY
+
+# The run-time 'check' directives from above happen after the parse-time
+# 'check' directives from below.
+#
+# expect-reset
+
+# Deferred evaluation during parsing
+#
# 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
@@ -20,8 +78,8 @@
#
# Conditions from .if directives 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
+# scope and from the environment are resolved, in this precedence 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.
#
@@ -49,7 +107,7 @@
.if $(@) != "\$\(@)"
. error
.endif
-# If the variable expression contains modifiers, the behavior depends on the
+# If the 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
@@ -77,17 +135,21 @@
.endif
+# Custom local variables
+#
# 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
+# assignment, the left-hand side must be a single word or empty.
+#
+# The empty variable name is expanded twice, once for 'one' and once for
+# 'two'.
+# expect: one: ignoring ' = three' as the variable name '' expands to empty
+# expect: two: ignoring ' = three' as the variable name '' expands to empty
one two:=three
-# If the two targets to the left are generated by a variable expression, the
+# If the two targets to the left are generated by an expression, the
# line is parsed as a variable assignment since its left-hand side is a single
# word.
# expect: Global: one two = three
@@ -137,13 +199,14 @@ 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}".
+ @echo "Making ${.TARGET} with make '"${VAR:Q}"' and env '$$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
+.export VAR
# 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
@@ -151,7 +214,7 @@ VAR= global
# irrelevant.
#
# expect-reset
-# expect: : Making var-scope-local-assign.o with VAR="local".
+# expect: Making var-scope-local-assign.o with make 'local' and env 'local'.
var-scope-local-assign.o: VAR= local
# Assignments using '+=' do *not* look up the global value, instead they only
@@ -161,9 +224,9 @@ var-scope-local-append.o: VAR+= local
# 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".
+# expect: Making var-scope-local-append.o with make 'local to var-scope-local-append.o' and env '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
+# To access the value of a global variable, use an expression. This
# expression is expanded before parsing the whole dependency line. Since the
# expansion happens to the right of the dependency operator ':', the expanded
# text does not influence parsing of the dependency line. Since the expansion
@@ -171,7 +234,7 @@ var-scope-local-append.o: VAR += to ${.TARGET}
# not influence the parsing of the variable assignment. 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".
+# expect: Making var-scope-local-append-global.o with make 'global+local' and env 'global+local'.
var-scope-local-append-global.o: VAR= ${VAR}+local
var-scope-local-default.o: VAR ?= first
@@ -179,7 +242,7 @@ 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".
+# expect: Making var-scope-local-default.o with make 'global' and env 'global'.
# Using the variable assignment operator ':=' provides another way of
# accessing a global variable and extending it with local modifications. The
@@ -187,7 +250,7 @@ var-scope-local-default.o: VAR ?= second
# dependency line as a whole. After that, the parser sees the variable
# assignment as 'VAR := ${VAR}+local' and searches for the variable 'VAR' in
# the usual scopes, picking up the variable from the global scope.
-# expect: : Making var-scope-local-subst.o with VAR="global+local".
+# expect: Making var-scope-local-subst.o with make 'global+local' and env 'global+local'.
var-scope-local-subst.o: VAR := $${VAR}+local
# The variable assignment operator '!=' assigns the output of the shell
@@ -199,38 +262,9 @@ 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.
-# expect: : var-scope-local-use.o uses .USE VAR="global"
+# expect: Making .USE var-scope-local-use.o with make 'global' and env 'global'.
a_use: .USE VAR=use
- : ${.TARGET} uses .USE VAR="${VAR}"
+ @echo "Making .USE ${.TARGET} with make '"${VAR:Q}"' and env '$$VAR'."
all: var-scope-local-use.o
var-scope-local-use.o: a_use
-
-
-# Since parse.c 1.656 from 2022-01-27 and before parse.c 1.662 from
-# 2022-02-05, there was an out-of-bounds read in Parse_IsVar when looking for
-# a variable assignment in a dependency line with trailing whitespace. Lines
-# without trailing whitespace were not affected. Global variable assignments
-# were guaranteed to have no trailing whitespace and were thus not affected.
-#
-# Try to reproduce some variants that may lead to a crash, depending on the
-# memory allocator. To get a crash, the terminating '\0' of the line must be
-# the last byte of a memory page. The expression '${:U}' forces this trailing
-# whitespace.
-
-# On FreeBSD x86_64, a crash could in some cases be forced using the following
-# line, which has length 47, so the terminating '\0' may end up at an address
-# of the form 0xXXXX_XXXX_XXXX_Xfff:
-Try_to_crash_FreeBSD.xxxxxxxxxxxxxxxxxx: 12345 ${:U}
-
-# The following line has length 4095, so line[4095] == '\0'. If the line is
-# allocated on a page boundary and the following page is not mapped, this line
-# leads to a segmentation fault.
-${:U:range=511:@_@1234567@:ts.}: 12345 ${:U}
-
-# The following line has length 8191, so line[8191] == '\0'. If the line is
-# allocated on a page boundary and the following page is not mapped, this line
-# leads to a segmentation fault.
-${:U:range=1023:@_@1234567@:ts.}: 12345 ${:U}
-
-12345:
diff --git a/contrib/bmake/unit-tests/varcmd.mk b/contrib/bmake/unit-tests/varcmd.mk
index 12739df30926..ec0cf96ed75c 100644
--- a/contrib/bmake/unit-tests/varcmd.mk
+++ b/contrib/bmake/unit-tests/varcmd.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varcmd.mk,v 1.6 2021/02/16 19:43:09 rillig Exp $
+# $NetBSD: varcmd.mk,v 1.7 2023/04/07 05:54:16 rillig Exp $
#
# Test behaviour of recursive make and vars set on command line.
#
@@ -12,6 +12,10 @@
# be rewritten to make it clear why there is a difference and why this is
# actually intended. Removing that large block of code makes only this test
# and vardebug.mk fail, which is not enough.
+#
+# See also:
+# var-scope-cmdline.mk
+# varname-makeflags.mk
FU= fu
FOO?= foo
diff --git a/contrib/bmake/unit-tests/vardebug.exp b/contrib/bmake/unit-tests/vardebug.exp
index 19e5c9c9fdd0..1a274de09f4d 100644
--- a/contrib/bmake/unit-tests/vardebug.exp
+++ b/contrib/bmake/unit-tests/vardebug.exp
@@ -1,67 +1,75 @@
-Global: delete FROM_CMDLINE (not found)
+Global: ignoring delete 'FROM_CMDLINE' as it is not found
Command: FROM_CMDLINE = # (empty)
Global: .MAKEOVERRIDES = FROM_CMDLINE
Global: VAR = added
Global: VAR = overwritten
Global: delete VAR
-Global: delete VAR (not found)
-Var_SetExpand: variable name "${:U}" expands to empty string, with value "empty name" - ignored
-Var_AppendExpand: variable name "${:U}" expands to empty string, with value "empty name" - ignored
-Global: FROM_CMDLINE = overwritten ignored!
+Global: ignoring delete 'VAR' as it is not found
+Global: ignoring ' = empty name' as the variable name '${:U}' expands to empty
+Global: ignoring ' += empty name' as the variable name '${:U}' expands to empty
+Global: ignoring 'FROM_CMDLINE = overwritten' due to a command line variable of the same name
Global: VAR = 1
Global: VAR = 1 2
Global: VAR = 1 2 3
-Var_Parse: ${VAR:M[2]} (eval-defined)
+Var_Parse: ${VAR:M[2]} (eval-defined-loud)
Evaluating modifier ${VAR:M...} on value "1 2 3"
Pattern for ':M' is "[2]"
ModifyWords: split "1 2 3" into 3 words
Result of ${VAR:M[2]} is "2"
-Var_Parse: ${VAR:N[2]} (eval-defined)
+Var_Parse: ${VAR:N[2]} (eval-defined-loud)
Evaluating modifier ${VAR:N...} on value "1 2 3"
Pattern for ':N' is "[2]"
ModifyWords: split "1 2 3" into 3 words
Result of ${VAR:N[2]} is "1 3"
-Var_Parse: ${VAR:S,2,two,} (eval-defined)
+Var_Parse: ${VAR:S,2,two,} (eval-defined-loud)
Evaluating modifier ${VAR:S...} on value "1 2 3"
Modifier part: "2"
Modifier part: "two"
ModifyWords: split "1 2 3" into 3 words
Result of ${VAR:S,2,two,} is "1 two 3"
-Var_Parse: ${VAR:Q} (eval-defined)
+Var_Parse: ${VAR:Q} (eval-defined-loud)
Evaluating modifier ${VAR:Q} on value "1 2 3"
Result of ${VAR:Q} is "1\ 2\ 3"
-Var_Parse: ${VAR:tu:tl:Q} (eval-defined)
+Var_Parse: ${VAR:tu:tl:Q} (eval-defined-loud)
Evaluating modifier ${VAR:t...} on value "1 2 3"
Result of ${VAR:tu} is "1 2 3"
Evaluating modifier ${VAR:t...} on value "1 2 3"
Result of ${VAR:tl} is "1 2 3"
Evaluating modifier ${VAR:Q} on value "1 2 3"
Result of ${VAR:Q} is "1\ 2\ 3"
-Var_Parse: ${:Uvalue:${:UM*e}:Mvalu[e]} (eval-defined)
-Evaluating modifier ${:U...} on value "" (eval-defined, undefined)
-Result of ${:Uvalue} is "value" (eval-defined, defined)
+Var_Parse: ${:Uvalue:${:UM*e}:Mvalu[e]} (eval-defined-loud)
+Evaluating modifier ${:U...} on value "" (eval, undefined)
+Result of ${:Uvalue} is "value" (eval, defined)
Indirect modifier "M*e" from "${:UM*e}"
-Evaluating modifier ${:M...} on value "value" (eval-defined, defined)
+Evaluating modifier ${:M...} on value "value" (eval, defined)
Pattern for ':M' is "*e"
ModifyWords: split "value" into 1 word
-Result of ${:M*e} is "value" (eval-defined, defined)
-Evaluating modifier ${:M...} on value "value" (eval-defined, defined)
+Result of ${:M*e} is "value" (eval, defined)
+Evaluating modifier ${:M...} on value "value" (eval, defined)
Pattern for ':M' is "valu[e]"
ModifyWords: split "value" into 1 word
-Result of ${:Mvalu[e]} is "value" (eval-defined, defined)
+Result of ${:Mvalu[e]} is "value" (eval, defined)
Global: delete VAR
-Var_Parse: ${:Uvariable:unknown} (eval-defined)
-Evaluating modifier ${:U...} on value "" (eval-defined, undefined)
-Result of ${:Uvariable} is "variable" (eval-defined, defined)
-Evaluating modifier ${:u...} on value "variable" (eval-defined, defined)
-make: "vardebug.mk" line 44: Unknown modifier "unknown"
-Result of ${:unknown} is error (eval-defined, defined)
-make: "vardebug.mk" line 44: Malformed conditional (${:Uvariable:unknown})
-Var_Parse: ${UNDEFINED} (eval-defined)
-make: "vardebug.mk" line 53: Malformed conditional (${UNDEFINED})
-Global: delete .SHELL (not found)
+Var_Parse: ${:Uvariable:unknown} (eval-defined-loud)
+Evaluating modifier ${:U...} on value "" (eval, undefined)
+Result of ${:Uvariable} is "variable" (eval, defined)
+Evaluating modifier ${:u...} on value "variable" (eval, defined)
+make: vardebug.mk:59: Unknown modifier ":unknown"
+ while evaluating "${:Uvariable:unknown}" with value "variable"
+Var_Parse: ${UNDEFINED} (eval-defined-loud)
+make: vardebug.mk:63: Variable "UNDEFINED" is undefined
+Global: ignoring delete '.SHELL' as it is not found
Command: .SHELL = </path/to/shell>
-Command: .SHELL = overwritten ignored (read-only)
+Command: ignoring '.SHELL = overwritten' as it is read-only
+Global: DYN = ${:U$@} $@ ${@}
+Var_Parse: ${DYN} (eval-keep-dollar-and-undefined)
+Var_Parse: ${:U$@} $@ ${@} (eval-keep-dollar-and-undefined)
+Evaluating modifier ${:U...} on value "" (eval-keep-dollar-and-undefined, undefined)
+Var_Parse: $@} $@ ${@} (eval-keep-dollar-and-undefined)
+Result of ${:U$@} is "$(.TARGET)" (eval-keep-dollar-and-undefined, defined)
+Var_Parse: $@ ${@} (eval-keep-dollar-and-undefined)
+Var_Parse: ${@} (eval-keep-dollar-and-undefined)
+Global: DYN = $(.TARGET) $(.TARGET) ${@}
Global: .MAKEFLAGS = -r -k -d v -d
Global: .MAKEFLAGS = -r -k -d v -d 0
make: Fatal errors encountered -- cannot continue
diff --git a/contrib/bmake/unit-tests/vardebug.mk b/contrib/bmake/unit-tests/vardebug.mk
index 4a16a7f2797f..3e242c378be5 100644
--- a/contrib/bmake/unit-tests/vardebug.mk
+++ b/contrib/bmake/unit-tests/vardebug.mk
@@ -1,32 +1,46 @@
-# $NetBSD: vardebug.mk,v 1.7 2021/02/04 21:42:47 rillig Exp $
+# $NetBSD: vardebug.mk,v 1.18 2025/03/29 19:08:52 rillig Exp $
#
# Demonstrates the debugging output for var.c.
.MAKEFLAGS: -dv FROM_CMDLINE=
+# expect: Global: VAR = added
VAR= added # VarAdd
+# expect: Global: VAR = overwritten
VAR= overwritten # Var_Set
-.undef VAR # Var_Delete (found)
-.undef VAR # Var_Delete (not found)
+# expect: Global: delete VAR
+.undef VAR
+# expect: Global: ignoring delete 'VAR' as it is not found
+.undef VAR
# The variable with the empty name cannot be set at all.
+# expect: Global: ignoring ' = empty name' as the variable name '${:U}' expands to empty
${:U}= empty name # Var_Set
+# expect: Global: ignoring ' += empty name' as the variable name '${:U}' expands to empty
${:U}+= empty name # Var_Append
FROM_CMDLINE= overwritten # Var_Set (ignored)
+# expect: Global: VAR = 1
VAR= 1
+# expect: Global: VAR = 1 2
VAR+= 2
+# expect: Global: VAR = 1 2 3
VAR+= 3
+# expect: Pattern for ':M' is "[2]"
+# expect: Result of ${VAR:M[2]} is "2"
.if ${VAR:M[2]} # ModifyWord_Match
.endif
-.if ${VAR:N[2]} # ModifyWord_NoMatch (no debug output)
+# expect: Pattern for ':N' is "[2]"
+# expect: Result of ${VAR:N[2]} is "1 3"
+.if ${VAR:N[2]} # ModifyWord_NoMatch
.endif
.if ${VAR:S,2,two,} # ParseModifierPart
.endif
+# expect: Result of ${VAR:Q} is "1\ 2\ 3"
.if ${VAR:Q} # VarQuote
.endif
@@ -34,31 +48,29 @@ VAR+= 3
.endif
# ApplyModifiers, "Got ..."
+# expect: Result of ${:Mvalu[e]} is "value" (eval, defined)
.if ${:Uvalue:${:UM*e}:Mvalu[e]}
.endif
+# expect: Global: delete VAR
.undef ${:UVAR} # Var_Delete
-# When ApplyModifiers results in an error, this appears in the debug log
-# as "is error", without surrounding quotes.
+# expect+1: Unknown modifier ":unknown"
.if ${:Uvariable:unknown}
.endif
-# XXX: The error message is "Malformed conditional", which is wrong.
-# The condition is syntactically fine, it just contains an undefined variable.
-#
-# There is a specialized error message for "Undefined variable", but as of
-# 2020-08-08, that is not covered by any unit tests. It might even be
-# unreachable.
+# expect+1: Variable "UNDEFINED" is undefined
.if ${UNDEFINED}
.endif
# By default, .SHELL is not defined and thus can be set. As soon as it is
# accessed, it is initialized in the command line scope (during VarFind),
# where it is set to read-only. Assigning to it is ignored.
+# expect: Command: ignoring '.SHELL = overwritten' as it is read-only
.MAKEFLAGS: .SHELL=overwritten
-.MAKEFLAGS: -d0
+DYN = ${:U$@} $@ ${@}
+# expect: Global: DYN = $(.TARGET) $(.TARGET) ${@}
+DYN := ${DYN}
-all:
- @:
+.MAKEFLAGS: -d0
diff --git a/contrib/bmake/unit-tests/varmisc.exp b/contrib/bmake/unit-tests/varmisc.exp
index f56f72d0ab9c..44b3c8e759cb 100644
--- a/contrib/bmake/unit-tests/varmisc.exp
+++ b/contrib/bmake/unit-tests/varmisc.exp
@@ -17,12 +17,9 @@ false
FALSE
do not evaluate or expand :? if discarding
is set
-year=2016 month=04 day=01
-date=20160401
Version=123.456.789 == 123456789
Literal=3.4.5 == 3004005
We have target specific vars
-MAN= make.1
save-dollars: 0 = $
save-dollars: 1 = $$
save-dollars: 2 = $$
@@ -46,29 +43,39 @@ export-appended: env mk
parse-dynamic: parse-dynamic parse-dynamic before
parse-dynamic: parse-dynamic parse-dynamic after
parse-dynamic: parse-dynamic parse-dynamic after
-varerror-unclosed:begin
+varerror-unclosed-1:begin
make: Unclosed variable ""
-
+ in command "@echo $("
+ in target "varerror-unclosed-2"
make: Unclosed variable "UNCLOSED"
-
+ in command "@echo $(UNCLOSED"
+ in target "varerror-unclosed-3"
make: Unclosed variable "UNCLOSED"
-
+ in command "@echo ${UNCLOSED"
+ in target "varerror-unclosed-4"
make: Unclosed variable "PATTERN"
-make: Unclosed variable expression, expecting '}' for modifier "M${PATTERN" of variable "UNCLOSED" with value ""
-
+ while evaluating variable "UNCLOSED" with value ""
+ in command "@echo ${UNCLOSED:M${PATTERN"
+ in target "varerror-unclosed-5"
+make: Unclosed expression, expecting "}" for modifier "M${PATTERN"
+ while evaluating variable "UNCLOSED" with value ""
+ in command "@echo ${UNCLOSED:M${PATTERN"
+ in target "varerror-unclosed-5"
make: Unclosed variable "param"
+ in command "@echo ${UNCLOSED.${param"
+ in target "varerror-unclosed-6"
make: Unclosed variable "UNCLOSED."
-
+ in command "@echo ${UNCLOSED.${param"
+ in target "varerror-unclosed-6"
make: Unclosed variable "UNCLOSED.1"
-
-make: Unclosed variable "UNCLOSED.2"
-
-make: Unclosed variable "UNCLOSED.3"
-
+ in command "@echo ${UNCLOSED.${:U1}"
+ in target "varerror-unclosed-7"
make: Unclosed variable "UNCLOSED_ORIG"
-
-varerror-unclosed:end
+ while evaluating variable "UNCLOSED_INDIR_1" with value "${UNCLOSED_ORIG"
+ while evaluating variable "UNCLOSED_INDIR_2" with value "${UNCLOSED_INDIR_1}"
+ in command "@echo ${UNCLOSED_INDIR_2}"
+ in target "varerror-unclosed-8"
target1-flags: we have: one two
target2-flags: we have: one two three four
-exit status 0
+exit status 2
diff --git a/contrib/bmake/unit-tests/varmisc.mk b/contrib/bmake/unit-tests/varmisc.mk
index 81818f3fb8bb..e36396633dc2 100644
--- a/contrib/bmake/unit-tests/varmisc.mk
+++ b/contrib/bmake/unit-tests/varmisc.mk
@@ -1,14 +1,13 @@
-# $Id: varmisc.mk,v 1.25 2021/12/07 00:03:11 sjg Exp $
-# $NetBSD: varmisc.mk,v 1.32 2021/12/05 10:02:51 rillig Exp $
+# $NetBSD: varmisc.mk,v 1.38 2025/06/28 22:39:29 rillig Exp $
#
# Miscellaneous variable tests.
all: unmatched_var_paren D_true U_true D_false U_false Q_lhs Q_rhs NQ_none \
- strftime cmpv manok
+ cmpv
all: save-dollars
all: export-appended
all: parse-dynamic
-all: varerror-unclosed
+all: varerror-unclosed-{1,2,3,4,5,6,7,8}
unmatched_var_paren:
@echo ${foo::=foo-text}
@@ -47,13 +46,6 @@ NQ_none:
@echo do not evaluate or expand :? if discarding
@echo ${VSET:U${1:L:?${True}:${False}}}
-April1= 1459494000
-
-# slightly contorted syntax to use utc via variable
-strftime:
- @echo ${year=%Y month=%m day=%d:L:gmtime=1459494000}
- @echo date=${%Y%m%d:L:${gmtime=${April1}:L}}
-
# big jumps to handle 3 digits per step
M_cmpv.units= 1 1000 1000000
M_cmpv= S,., ,g:_:range:@i@+ $${_:[-$$i]} \* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh
@@ -66,17 +58,6 @@ cmpv:
@echo Literal=3.4.5 == ${3.4.5:L:${M_cmpv}}
@echo We have ${${.TARGET:T}.only}
-# catch mishandling of nested variables in .for loop
-MAN=
-MAN1= make.1
-.for s in 1 2
-. if defined(MAN$s) && !empty(MAN$s)
-MAN+= ${MAN$s}
-. endif
-.endfor
-
-manok:
- @echo MAN=${MAN}
# Test parsing of boolean values.
# begin .MAKE.SAVE_DOLLARS; see Var_SetWithFlags and ParseBoolean.
@@ -131,10 +112,10 @@ VAR.${PARAM}+= 2
.if ${VAR.+} != "1 2"
. error "${VAR.+}"
.endif
-.for param in + ! ?
+.for param in : + ! ?
VAR.${param}= ${param}
.endfor
-.if ${VAR.+} != "+" || ${VAR.!} != "!" || ${VAR.?} != "?"
+.if ${VAR.${:U\:}} != ":" || ${VAR.+} != "+" || ${VAR.!} != "!" || ${VAR.?} != "?"
. error "${VAR.+}" "${VAR.!}" "${VAR.?}"
.endif
@@ -207,16 +188,30 @@ target1-flags: target1.c
target2-flags: target2.c
@echo $@: we have: ${FLAGS}
-varerror-unclosed:
+varerror-unclosed-1:
@echo $@:begin
+varerror-unclosed-2:
+# expect: make: Unclosed variable ""
@echo $(
+varerror-unclosed-3:
+# expect: make: Unclosed variable "UNCLOSED"
@echo $(UNCLOSED
+varerror-unclosed-4:
+# expect: make: Unclosed variable "UNCLOSED"
@echo ${UNCLOSED
+varerror-unclosed-5:
+# expect: make: Unclosed expression, expecting "}" for modifier "M${PATTERN"
@echo ${UNCLOSED:M${PATTERN
+varerror-unclosed-6:
+# expect: make: Unclosed variable "param"
+# expect: make: Unclosed variable "UNCLOSED."
@echo ${UNCLOSED.${param
+varerror-unclosed-7:
@echo $
.for i in 1 2 3
+# expect: make: Unclosed variable "UNCLOSED.1"
@echo ${UNCLOSED.${i}
.endfor
+varerror-unclosed-8:
@echo ${UNCLOSED_INDIR_2}
@echo $@:end
diff --git a/contrib/bmake/unit-tests/varmod-assign-shell.exp b/contrib/bmake/unit-tests/varmod-assign-shell.exp
index 7bb41108cb62..6e9fdc4dbb1d 100644
--- a/contrib/bmake/unit-tests/varmod-assign-shell.exp
+++ b/contrib/bmake/unit-tests/varmod-assign-shell.exp
@@ -1,11 +1,11 @@
-make: "varmod-assign-shell.mk" line 27: warning: "echo output; false" returned non-zero status
+make: varmod-assign-shell.mk:21: warning: Command "echo output; (exit 13)" exited with status 13
Global: _ = # (empty)
-Var_Parse: ${ASSIGNED::!=echo output; ${:Ufalse}} (eval-keep-dollar-and-undefined)
+Var_Parse: ${ASSIGNED::!=echo output; ${:U(exit 13)}} (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)
+Modifier part: "echo output; (exit 13)"
+Capturing the output of command "echo output; (exit 13)"
+make: varmod-assign-shell.mk:26: warning: Command "echo output; (exit 13)" exited with status 13
+Result of ${ASSIGNED::!=echo output; ${:U(exit 13)}} is "" (eval-keep-dollar-and-undefined, regular)
Global: _ = # (empty)
Global: .MAKEFLAGS = -r -k -d v -d
Global: .MAKEFLAGS = -r -k -d v -d 0
diff --git a/contrib/bmake/unit-tests/varmod-assign-shell.mk b/contrib/bmake/unit-tests/varmod-assign-shell.mk
index d03692942d5b..7bbea0ff9463 100644
--- a/contrib/bmake/unit-tests/varmod-assign-shell.mk
+++ b/contrib/bmake/unit-tests/varmod-assign-shell.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-assign-shell.mk,v 1.4 2022/01/10 20:32:29 rillig Exp $
+# $NetBSD: varmod-assign-shell.mk,v 1.11 2025/01/11 21:21:33 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
@@ -15,20 +15,15 @@
# 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
+# expect+1: warning: Command "echo output; (exit 13)" exited with status 13
+DIRECT!= echo output; (exit 13)
ASSIGNED= previous
-.MAKEFLAGS: -dv # to see the actual command
-_:= ${ASSIGNED::!=echo output; ${:Ufalse}}
+.MAKEFLAGS: -dv # to see the "Capturing" debug output
+# expect+1: warning: Command "echo output; (exit 13)" exited with status 13
+_:= ${ASSIGNED::!=echo output; ${:U(exit 13)}}
.MAKEFLAGS: -d0
all:
diff --git a/contrib/bmake/unit-tests/varmod-assign.exp b/contrib/bmake/unit-tests/varmod-assign.exp
index 1ad388418ab5..ae7f6787d124 100644
--- a/contrib/bmake/unit-tests/varmod-assign.exp
+++ b/contrib/bmake/unit-tests/varmod-assign.exp
@@ -2,27 +2,74 @@ Global: param = twice
Global: VARNAME = VAR.$${param}
Var_Parse: ${VARNAME} (eval)
Global: VAR.${param} = initial-value
-Var_Parse: ${${VARNAME}::=assigned-value} (eval-defined)
-Var_Parse: ${VARNAME}::=assigned-value} (eval-defined)
+Var_Parse: ${${VARNAME}::=assigned-value} (eval-defined-loud)
+Var_Parse: ${VARNAME}::=assigned-value} (eval)
Evaluating modifier ${VAR.${param}::...} on value "initial-value"
Modifier part: "assigned-value"
Global: VAR.${param} = assigned-value
Result of ${VAR.${param}::=assigned-value} is ""
-Var_Parse: ${${VARNAME}} != "assigned-value" (eval-defined)
-Var_Parse: ${VARNAME}} != "assigned-value" (eval-defined)
+Var_Parse: ${${VARNAME}} != "assigned-value" (eval-defined-loud)
+Var_Parse: ${VARNAME}} != "assigned-value" (eval)
Global: .MAKEFLAGS = -r -k -d v -d
Global: .MAKEFLAGS = -r -k -d v -d 0
-make: Bad modifier ":" for variable ""
-mod-assign-empty: value}
-make: Bad modifier ":" for variable ""
-mod-assign-empty: overwritten}
-mod-assign-empty: VAR=overwritten
-make: Unknown modifier ":x"
-
+Var_Parse: ${CMD_CMD_VAR::=new-value} || ${CMD_GLOBAL_VAR::=new-value} || ${CMD_ENV_VAR::=new-value} || "${CMD_NEW_VAR::=new-value}" (eval-defined-loud)
+Evaluating modifier ${CMD_CMD_VAR::...} on value "cmd-value"
+Modifier part: "new-value"
+Command: CMD_CMD_VAR = new-value
+Global: .MAKEOVERRIDES = FIRST LAST LAST LAST APPENDED RAN RAN RAN IT1 THEN1 IE2 ELSE2 CMD_CMD_VAR CMD_CMD_VAR
+Result of ${CMD_CMD_VAR::=new-value} is ""
+Var_Parse: ${CMD_GLOBAL_VAR::=new-value} || ${CMD_ENV_VAR::=new-value} || "${CMD_NEW_VAR::=new-value}" (eval-defined-loud)
+Evaluating modifier ${CMD_GLOBAL_VAR::...} on value "global-value"
+Modifier part: "new-value"
+Global: CMD_GLOBAL_VAR = new-value
+Result of ${CMD_GLOBAL_VAR::=new-value} is ""
+Var_Parse: ${CMD_ENV_VAR::=new-value} || "${CMD_NEW_VAR::=new-value}" (eval-defined-loud)
+Evaluating modifier ${CMD_ENV_VAR::...} on value "env-value"
+Modifier part: "new-value"
+Global: CMD_ENV_VAR = new-value
+Result of ${CMD_ENV_VAR::=new-value} is ""
+Var_Parse: ${CMD_NEW_VAR::=new-value}" (eval)
+Evaluating modifier ${CMD_NEW_VAR::...} on value "" (eval, undefined)
+Modifier part: "new-value"
+Global: ignoring delete 'CMD_NEW_VAR' as it is not found
+Command: CMD_NEW_VAR = new-value
+Global: .MAKEOVERRIDES = FIRST LAST LAST LAST APPENDED RAN RAN RAN IT1 THEN1 IE2 ELSE2 CMD_CMD_VAR CMD_CMD_VAR CMD_NEW_VAR
+Result of ${CMD_NEW_VAR::=new-value} is "" (eval, undefined)
+Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d
+Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d 0
+make: Invalid attempt to assign "value" to variable "" via modifier "::="
+ while evaluating "${::=value}" with value ""
+ in command "@echo $@: ${::=value}"
+ in target "mod-assign-empty-1"
+make: Invalid attempt to assign "overwritten" to variable "" via modifier "::="
+ while evaluating "${:Uvalue::=overwritten}" with value "value"
+ in command "@echo $@: ${:Uvalue::=overwritten}"
+ in target "mod-assign-empty-2"
+make: Invalid attempt to assign "appended" to variable "" via modifier "::+="
+ while evaluating "${:Uvalue::+=appended}" with value "value"
+ in command "@echo $@: ${:Uvalue::+=appended}"
+ in target "mod-assign-empty-3"
+mod-assign-empty-4: VAR=overwritten
+make: Unknown modifier "::x"
+ while evaluating variable "ASSIGN" with value ""
+ in command "@echo ${ASSIGN::x}"
+ in target "mod-assign-parse-1"
sysv:y
-make: Unfinished modifier for "ASSIGN" ('}' missing)
-
+make: Unfinished modifier after "value # missing closing brace", expecting "}"
+ while evaluating variable "ASSIGN" with value ""
+ in command "@echo ${ASSIGN::=value # missing closing brace"
+ in target "mod-assign-parse-3"
ok=word
-make: " echo word; false " returned non-zero status
+make: warning: Command " echo word; (exit 13) " exited with status 13
+ while evaluating variable "SH_ERR" with value "previous"
+ in command "@${SH_ERR::!= echo word; (exit 13) } echo err=${SH_ERR}"
+ in target "mod-assign-shell-error"
err=previous
-exit status 0
+Command: TARGET_CMD_VAR = cmd-value
+Global: TARGET_GLOBAL_VAR = global-value
+target: TARGET_TARGET_VAR = target-value
+target: TARGET_TARGET_VAR = new-value
+Global: TARGET_GLOBAL_VAR = new-value
+Global: TARGET_ENV_VAR = new-value
+target: TARGET_NEW_VAR = new-value
+exit status 2
diff --git a/contrib/bmake/unit-tests/varmod-assign.mk b/contrib/bmake/unit-tests/varmod-assign.mk
index a6236253068d..af2c90385315 100644
--- a/contrib/bmake/unit-tests/varmod-assign.mk
+++ b/contrib/bmake/unit-tests/varmod-assign.mk
@@ -1,40 +1,46 @@
-# $NetBSD: varmod-assign.mk,v 1.15 2022/02/09 21:09:24 rillig Exp $
+# $NetBSD: varmod-assign.mk,v 1.28 2025/03/30 01:09:41 rillig Exp $
#
# Tests for the obscure ::= variable modifiers, which perform variable
# assignments during evaluation, just like the = operator in C.
-all: mod-assign-empty
-all: mod-assign-parse
+.if !make(target)
+
+all: mod-assign-empty-{1,2,3,4}
+all: mod-assign-parse-{1,2,3}
all: mod-assign-shell-error
-# The modifier '::?=' applies the assignment operator '?=' 3 times. The
+# In the following loop expression,
+# the '::?=' modifier applies the assignment operator '?=' 3 times. The
# operator '?=' only has an effect for the first time, therefore the variable
# FIRST ends up with the value 1.
.if "${1 2 3:L:@i@${FIRST::?=$i}@} first=${FIRST}" != " first=1"
. error
.endif
-# The modifier '::=' applies the assignment operator '=' 3 times. The
+# In the following loop expression,
+# the modifier '::=' applies the assignment operator '=' 3 times. The
# operator '=' overwrites the previous value, therefore the variable LAST ends
# up with the value 3.
.if "${1 2 3:L:@i@${LAST::=$i}@} last=${LAST}" != " last=3"
. error
.endif
-# The modifier '::+=' applies the assignment operator '+=' 3 times. The
+# In the following loop expression,
+# the modifier '::+=' applies the assignment operator '+=' 3 times. The
# operator '+=' appends 3 times to the variable, therefore the variable
# APPENDED ends up with the value "1 2 3".
.if "${1 2 3:L:@i@${APPENDED::+=$i}@} appended=${APPENDED}" != " appended=1 2 3"
. error
.endif
-# The modifier '::!=' applies the assignment operator '!=' 3 times. Just as
+# In the following loop expression,
+# the modifier '::!=' applies the assignment operator '!=' 3 times. Just as
# with the modifier '::=', the last value is stored in the RAN variable.
.if "${1 2 3:L:@i@${RAN::!=${i:%=echo '<%>';}}@} ran=${RAN}" != " ran=<3>"
. error
.endif
-# The assignments were performed as part of .if conditions and thus happened
+# When a '::=' modifier is evaluated as part of an .if condition, it happens
# in the command line scope.
.if "${FIRST}, ${LAST}, ${APPENDED}, ${RAN}" != "1, 3, 1 2 3, <3>"
. error
@@ -65,32 +71,44 @@ SINK4:= ${0:?${THEN4::=then4${IT4::=t4}}:${ELSE4::=else4${IE4::=e4}}} ${THEN4}${
. error
.endif
-mod-assign-empty:
+mod-assign-empty-1:
# Assigning to the empty variable would obviously not work since that
- # variable is write-protected. Therefore it is rejected early with a
- # "Bad modifier" message.
+ # variable is write-protected.
+# expect: make: Invalid attempt to assign "value" to variable "" via modifier "::="
@echo $@: ${::=value}
+mod-assign-empty-2:
# In this variant, it is not as obvious that the name of the
- # expression is empty. Assigning to it is rejected as well, with the
- # same "Bad modifier" message.
+ # expression is empty.
+# expect: make: Invalid attempt to assign "overwritten" to variable "" via modifier "::="
@echo $@: ${:Uvalue::=overwritten}
+mod-assign-empty-3:
+ # In this variant, it is not as obvious that the name of the
+ # expression is empty.
+# expect: make: Invalid attempt to assign "appended" to variable "" via modifier "::+="
+ @echo $@: ${:Uvalue::+=appended}
+
+mod-assign-empty-4:
# The :L modifier sets the value of the expression to its variable
# name. The name of the expression is "VAR", therefore assigning to
# that variable works.
+# expect: mod-assign-empty-4: VAR=overwritten
@echo $@: ${VAR:L::=overwritten} VAR=${VAR}
-mod-assign-parse:
+mod-assign-parse-1:
# The modifier for assignment operators starts with a ':'.
# An 'x' after that is an invalid modifier.
- # expect: make: Unknown modifier ":x"
+# expect: make: Unknown modifier "::x"
@echo ${ASSIGN::x}
+mod-assign-parse-2:
# When parsing an assignment operator fails because the operator is
# incomplete, make falls back to the SysV modifier.
@echo ${SYSV::=sysv\:x}${SYSV::x=:y}
+mod-assign-parse-3:
+# expect: make: Unfinished modifier after "value # missing closing brace", expecting "}"
@echo ${ASSIGN::=value # missing closing brace
mod-assign-shell-error:
@@ -99,7 +117,7 @@ mod-assign-shell-error:
# If the command fails, the variable keeps its previous value.
@${SH_ERR::=previous}
- @${SH_ERR::!= echo word; false } echo err=${SH_ERR}
+ @${SH_ERR::!= echo word; (exit 13) } echo err=${SH_ERR}
# XXX: The ::= modifier expands its right-hand side exactly once.
# This differs subtly from normal assignments such as '+=' or '=', which copy
@@ -116,7 +134,7 @@ APPEND.dollar= $${APPEND.indirect}
.endif
-# The assignment modifier can be used in a variable expression that is
+# The assignment modifier can be used in an expression that is
# enclosed in parentheses. In such a case, parsing stops at the first ')',
# not at the first '}'.
VAR= previous
@@ -149,3 +167,54 @@ ${VARNAME}= initial-value # Sets 'VAR.${param}' to 'expanded'.
. error
.endif
.MAKEFLAGS: -d0
+
+
+# Conditional directives are evaluated in command line scope. An assignment
+# modifier that creates a new variable creates it in the command line scope.
+# Existing variables are updated in their previous scope, and environment
+# variables are created in the global scope, as in other situations.
+.MAKEFLAGS: CMD_CMD_VAR=cmd-value
+CMD_GLOBAL_VAR=global-value
+export CMD_ENV_VAR=env-value
+.MAKEFLAGS: -dv
+# expect-reset
+# expect: Command: CMD_CMD_VAR = new-value
+# expect: Global: CMD_GLOBAL_VAR = new-value
+# expect: Global: CMD_ENV_VAR = new-value
+# expect: Global: ignoring delete 'CMD_NEW_VAR' as it is not found
+# expect: Command: CMD_NEW_VAR = new-value
+.if ${CMD_CMD_VAR::=new-value} \
+ || ${CMD_GLOBAL_VAR::=new-value} \
+ || ${CMD_ENV_VAR::=new-value} \
+ || "${CMD_NEW_VAR::=new-value}"
+. error
+.endif
+.MAKEFLAGS: -d0
+
+# Run the 'target' test in a separate sub-make, with reduced debug logging.
+all: run-target
+run-target: .PHONY
+ @${MAKE} -r -f ${MAKEFILE} -dv target 2>&1 | grep ': TARGET_'
+
+.else # make(target)
+
+# The commands of a target are evaluated in target scope. An assignment
+# modifier that creates a new variable creates it in the target scope.
+# Existing variables are updated in their previous scope, and environment
+# variables are created in the global scope, as in other situations.
+#
+# expect: target: TARGET_TARGET_VAR = new-value
+# expect: Global: TARGET_GLOBAL_VAR = new-value
+# expect: Global: TARGET_ENV_VAR = new-value
+# expect: target: TARGET_NEW_VAR = new-value
+.MAKEFLAGS: TARGET_CMD_VAR=cmd-value
+TARGET_GLOBAL_VAR=global-value
+export TARGET_ENV_VAR=env-value
+target: .PHONY TARGET_TARGET_VAR=target-value
+ : ${TARGET_TARGET_VAR::=new-value}
+ : ${TARGET_CMD_VAR::=new-value}
+ : ${TARGET_GLOBAL_VAR::=new-value}
+ : ${TARGET_ENV_VAR::=new-value}
+ : ${TARGET_NEW_VAR::=new-value}
+
+.endif
diff --git a/contrib/bmake/unit-tests/varmod-defined.exp b/contrib/bmake/unit-tests/varmod-defined.exp
index b44d58c657aa..d82a292292a4 100644
--- a/contrib/bmake/unit-tests/varmod-defined.exp
+++ b/contrib/bmake/unit-tests/varmod-defined.exp
@@ -14,7 +14,7 @@ Modifier part: "${8_DOLLARS}"
ModifyWords: split "$$$$$$$$" into 1 word
Global: var = $$$$$$$$
Var_Parse: ${8_DOLLARS} (eval-keep-undefined)
-ModifyWord_Loop: in "$$$$$$$$", replace "var" with "${8_DOLLARS}" to "$$$$"
+ModifyWord_Loop: expand "${8_DOLLARS}" to "$$$$"
Global: delete var
Result of ${VAR:@var@${8_DOLLARS}@} is "$$$$" (eval-keep-dollar-and-undefined, regular)
Global: VAR = $$$$
diff --git a/contrib/bmake/unit-tests/varmod-defined.mk b/contrib/bmake/unit-tests/varmod-defined.mk
index ab5d708cf73f..2ee9def9e164 100644
--- a/contrib/bmake/unit-tests/varmod-defined.mk
+++ b/contrib/bmake/unit-tests/varmod-defined.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-defined.mk,v 1.12 2021/11/30 23:52:19 rillig Exp $
+# $NetBSD: varmod-defined.mk,v 1.16 2023/11/19 21:47:52 rillig Exp $
#
# Tests for the :D variable modifier, which returns the given string
# if the variable is defined. It is closely related to the :U modifier.
@@ -46,10 +46,10 @@ DEF= defined
. error
.endif
-# Like in several other places in variable expressions, when
+# Like in several other places in expressions, when
# ApplyModifier_Defined calls Var_Parse, double dollars lead to a parse
# error that is silently ignored. This makes all dollar signs disappear,
-# except for the last, which is a well-formed variable expression.
+# except for the last, which is a well-formed expression.
#
.if ${DEF:D$$$$$${DEF}} != "defined"
. error
@@ -58,7 +58,7 @@ DEF= defined
# Any other text is written without any further escaping. In contrast
# to the :M modifier, parentheses and braces do not need to be nested.
# Instead, the :D modifier is implemented sanely by parsing nested
-# expressions as such, without trying any shortcuts. See ApplyModifier_Match
+# expressions as such, without trying any shortcuts. See ParseModifier_Match
# for an inferior variant.
#
.if ${DEF:D!&((((} != "!&(((("
@@ -104,5 +104,13 @@ VAR:= ${VAR:D${8_DOLLARS}}
VAR:= ${VAR:@var@${8_DOLLARS}@}
.MAKEFLAGS: -d0
-all:
- @:;
+
+# Before var.c 1.1030 from 2022-08-24, the following expression caused an
+# out-of-bounds read when parsing the indirect ':U' modifier.
+M_U_backslash:= ${:UU\\}
+.if ${:${M_U_backslash}} != "\\"
+. error
+.endif
+
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/varmod-edge.exp b/contrib/bmake/unit-tests/varmod-edge.exp
index d9db72b2e2ef..b80380fb702c 100644
--- a/contrib/bmake/unit-tests/varmod-edge.exp
+++ b/contrib/bmake/unit-tests/varmod-edge.exp
@@ -1,27 +1,20 @@
-make: "varmod-edge.mk" line 166: ok M-paren
-make: "varmod-edge.mk" line 166: ok M-mixed
-make: "varmod-edge.mk" line 166: ok M-unescape
-make: Unclosed variable expression, expecting '}' for modifier "U*)" of variable "" with value "*)"
-make: "varmod-edge.mk" line 166: ok M-nest-mix
-make: "varmod-edge.mk" line 166: ok M-nest-brk
-make: "varmod-edge.mk" line 166: ok M-pat-err
-make: "varmod-edge.mk" line 166: ok M-bsbs
-make: "varmod-edge.mk" line 166: ok M-bs1-par
-make: "varmod-edge.mk" line 166: ok M-bs2-par
-make: "varmod-edge.mk" line 166: ok M-128
-make: "varmod-edge.mk" line 166: ok eq-ext
-make: "varmod-edge.mk" line 166: ok eq-q
-make: "varmod-edge.mk" line 166: ok eq-bs
-make: Unfinished modifier for "INP.eq-esc" ('=' missing)
-make: "varmod-edge.mk" line 166: ok eq-esc
-make: "varmod-edge.mk" line 166: ok colon
-make: "varmod-edge.mk" line 165: Unknown modifier ":"
-make: "varmod-edge.mk" line 165: Unknown modifier ":"
-make: "varmod-edge.mk" line 166: ok colons
-make: "varmod-edge.mk" line 175: Unknown modifier "Z"
-make: "varmod-edge.mk" line 175: Malformed conditional (${:Z})
-make: Unfinished modifier for "" (',' missing)
-make: "varmod-edge.mk" line 188: Malformed conditional (${:S,})
+make: varmod-edge.mk:60: Unclosed expression, expecting "}" for modifier "U*)"
+ while evaluating "${:U*)" with value "*)"
+ while evaluating variable "INP" with value "(parentheses)"
+ while evaluating variable "MOD" with value "${INP:M${:U*)}}"
+make: varmod-edge.mk:88: Unfinished character list in pattern "[[" of modifier ":M"
+ while evaluating variable "INP" with value "[ [[ [[["
+ while evaluating variable "MOD" with value "${INP:M${:U[[}}"
+make: varmod-edge.mk:178: Unfinished modifier after "a\=b}", expecting "="
+ while evaluating variable "INP" with value "file.c file..."
+ while evaluating variable "MOD" with value "${INP:a\=b}"
+make: varmod-edge.mk:193: Unknown modifier "::"
+ while evaluating variable "INP" with value "value"
+ while evaluating variable "MOD" with value "${INP::::}"
+make: varmod-edge.mk:199: Unknown modifier ":Z"
+ while evaluating "${:Z}" with value ""
+make: varmod-edge.mk:212: Unfinished modifier after "}", expecting ","
+ while evaluating "${:S,}" with value ""
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varmod-edge.mk b/contrib/bmake/unit-tests/varmod-edge.mk
index 762053d281a3..b5f879372afd 100644
--- a/contrib/bmake/unit-tests/varmod-edge.mk
+++ b/contrib/bmake/unit-tests/varmod-edge.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-edge.mk,v 1.16 2021/02/23 15:56:30 rillig Exp $
+# $NetBSD: varmod-edge.mk,v 1.37 2025/06/28 22:39:29 rillig Exp $
#
# Tests for edge cases in variable modifiers.
#
@@ -10,19 +10,23 @@
# - MOD, the expression for testing the modifier
# - EXP, the expected output
-TESTS+= M-paren
-INP.M-paren= (parentheses) {braces} (opening closing) ()
-MOD.M-paren= ${INP.M-paren:M(*)}
-EXP.M-paren= (parentheses) ()
+INP= (parentheses) {braces} (opening closing) ()
+MOD= ${INP:M(*)}
+EXP= (parentheses) ()
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
# The first closing brace matches the opening parenthesis.
-# The second closing brace actually ends the variable expression.
+# The second closing brace actually ends the expression.
#
# XXX: This is unexpected but rarely occurs in practice.
-TESTS+= M-mixed
-INP.M-mixed= (paren-brace} (
-MOD.M-mixed= ${INP.M-mixed:M(*}}
-EXP.M-mixed= (paren-brace}
+INP= (paren-brace} (
+MOD= ${INP:M(*}}
+EXP= (paren-brace}
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
# After the :M modifier has parsed the pattern, only the closing brace
# and the colon are unescaped. The other characters are left as-is.
@@ -31,66 +35,77 @@ EXP.M-mixed= (paren-brace}
# Str_Match.
#
# XXX: This is unexpected. The opening brace should also be unescaped.
-TESTS+= M-unescape
-INP.M-unescape= ({}): \(\{\}\)\: \(\{}\):
-MOD.M-unescape= ${INP.M-unescape:M\\(\\{\\}\\)\\:}
-EXP.M-unescape= \(\{}\):
+INP= ({}): \(\{\}\)\: \(\{}\):
+MOD= ${INP:M\\(\\{\\}\\)\\:}
+EXP= \(\{}\):
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
# When the :M and :N modifiers are parsed, the pattern finishes as soon
# as open_parens + open_braces == closing_parens + closing_braces. This
# means that ( and } form a matching pair.
#
-# Nested variable expressions are not parsed as such. Instead, only the
+# Nested expressions are not parsed as such. Instead, only the
# parentheses and braces are counted. This leads to a parse error since
# the nested expression is not "${:U*)}" but only "${:U*)", which is
# missing the closing brace. The expression is evaluated anyway.
# The final brace in the output comes from the end of M.nest-mix.
#
# XXX: This is unexpected but rarely occurs in practice.
-TESTS+= M-nest-mix
-INP.M-nest-mix= (parentheses)
-MOD.M-nest-mix= ${INP.M-nest-mix:M${:U*)}}
-EXP.M-nest-mix= (parentheses)}
-# make: Unclosed variable expression, expecting '}' for modifier "U*)" of variable "" with value "*)"
+INP= (parentheses)
+MOD= ${INP:M${:U*)}}
+EXP= (parentheses)}
+# expect+1: Unclosed expression, expecting "}" for modifier "U*)"
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
+
# In contrast to parentheses and braces, the brackets are not counted
-# when the :M modifier is parsed since Makefile variables only take the
+# when the :M modifier is parsed since Makefile expressions only take the
# ${VAR} or $(VAR) forms, but not $[VAR].
#
# The final ] in the pattern is needed to close the character class.
-TESTS+= M-nest-brk
-INP.M-nest-brk= [ [[ [[[
-MOD.M-nest-brk= ${INP.M-nest-brk:M${:U[[[[[]}}
-EXP.M-nest-brk= [
+INP= [ [[ [[[
+MOD= ${INP:M${:U[[[[[]}}
+EXP= [
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
+
# The pattern in the nested variable has an unclosed character class.
-# No error is reported though, and the pattern is closed implicitly.
#
-# XXX: It is unexpected that no error is reported.
-# See str.c, function Str_Match.
+# Before str.c 1.104 from 2024-07-06, no error was reported.
#
# Before 2019-12-02, this test case triggered an out-of-bounds read
# in Str_Match.
-TESTS+= M-pat-err
-INP.M-pat-err= [ [[ [[[
-MOD.M-pat-err= ${INP.M-pat-err:M${:U[[}}
-EXP.M-pat-err= [
+INP= [ [[ [[[
+MOD= ${INP:M${:U[[}}
+EXP= [
+# expect+1: Unfinished character list in pattern "[[" of modifier ":M"
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
# The first backslash does not escape the second backslash.
# Therefore, the second backslash escapes the parenthesis.
# This means that the pattern ends there.
-# The final } in the output comes from the end of MOD.M-bsbs.
+# The final } in the output comes from the end of MOD.
#
# If the first backslash were to escape the second backslash, the first
-# closing brace would match the opening parenthesis (see M-mixed), and
+# closing brace would match the opening parenthesis (see paren-brace), and
# the second closing brace would be needed to close the variable.
# After that, the remaining backslash would escape the parenthesis in
# the pattern, therefore (} would match.
-TESTS+= M-bsbs
-INP.M-bsbs= (} \( \(}
-MOD.M-bsbs= ${INP.M-bsbs:M\\(}}
-EXP.M-bsbs= \(}
-#EXP.M-bsbs= (} # If the first backslash were to escape ...
+INP= (} \( \(}
+MOD= ${INP:M\\(}}
+EXP= \(}
+#EXP= (} # If the first backslash were to escape ...
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
# The backslash in \( does not escape the parenthesis, therefore it
# counts for the nesting level and matches with the first closing brace.
@@ -99,79 +114,88 @@ EXP.M-bsbs= \(}
#
# The second :M in the pattern is nested between ( and }, therefore it
# does not start a new modifier.
-TESTS+= M-bs1-par
-INP.M-bs1-par= ( (:M (:M} \( \(:M \(:M}
-MOD.M-bs1-par= ${INP.M-bs1-par:M\(:M*}}}
-EXP.M-bs1-par= (:M}}
+INP= ( (:M (:M} \( \(:M \(:M}
+MOD= ${INP:M\(:M*}}}
+EXP= (:M}}
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
# The double backslash is passed verbatim to the pattern matcher.
# The Str_Match pattern is \\(:M*}, and there the backslash is unescaped.
# Again, the ( takes place in the nesting level, and there is no way to
# prevent this, no matter how many backslashes are used.
-TESTS+= M-bs2-par
-INP.M-bs2-par= ( (:M (:M} \( \(:M \(:M}
-MOD.M-bs2-par= ${INP.M-bs2-par:M\\(:M*}}}
-EXP.M-bs2-par= \(:M}}
-
-# Str_Match uses a recursive algorithm for matching the * patterns.
-# Make sure that it survives patterns with 128 asterisks.
-# That should be enough for all practical purposes.
-# To produce a stack overflow, just add more :Qs below.
-TESTS+= M-128
-INP.M-128= ${:U\\:Q:Q:Q:Q:Q:Q:Q:S,\\,x,g}
-PAT.M-128= ${:U\\:Q:Q:Q:Q:Q:Q:Q:S,\\,*,g}
-MOD.M-128= ${INP.M-128:M${PAT.M-128}}
-EXP.M-128= ${INP.M-128}
+INP= ( (:M (:M} \( \(:M \(:M}
+MOD= ${INP:M\\(:M*}}}
+EXP= \(:M}}
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
+
+# Before str.c 1.48 from 2020-06-15, Str_Match used a recursive algorithm for
+# matching the '*' patterns and did not optimize for multiple '*' in a row.
+# Test a pattern with 65536 asterisks.
+INP= ${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,x,g}
+PAT= ${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,*,g}
+MOD= ${INP:M${PAT}}
+EXP= ${INP}
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
# This is the normal SysV substitution. Nothing surprising here.
-TESTS+= eq-ext
-INP.eq-ext= file.c file.cc
-MOD.eq-ext= ${INP.eq-ext:%.c=%.o}
-EXP.eq-ext= file.o file.cc
+INP= file.c file.cc
+MOD= ${INP:%.c=%.o}
+EXP= file.o file.cc
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
# The SysV := modifier is greedy and consumes all the modifier text
# up until the closing brace or parenthesis. The :Q may look like a
# modifier, but it really isn't, that's why it appears in the output.
-TESTS+= eq-q
-INP.eq-q= file.c file.cc
-MOD.eq-q= ${INP.eq-q:%.c=%.o:Q}
-EXP.eq-q= file.o:Q file.cc
+INP= file.c file.cc
+MOD= ${INP:%.c=%.o:Q}
+EXP= file.o:Q file.cc
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
# The = in the := modifier can be escaped.
-TESTS+= eq-bs
-INP.eq-bs= file.c file.c=%.o
-MOD.eq-bs= ${INP.eq-bs:%.c\=%.o=%.ext}
-EXP.eq-bs= file.c file.ext
+INP= file.c file.c=%.o
+MOD= ${INP:%.c\=%.o=%.ext}
+EXP= file.c file.ext
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
# Having only an escaped '=' results in a parse error.
# The call to "pattern.lhs = ParseModifierPart" fails.
-TESTS+= eq-esc
-INP.eq-esc= file.c file...
-MOD.eq-esc= ${INP.eq-esc:a\=b}
-EXP.eq-esc= # empty
-# make: Unfinished modifier for INP.eq-esc ('=' missing)
-
-TESTS+= colon
-INP.colon= value
-MOD.colon= ${INP.colon:}
-EXP.colon= value
-
-TESTS+= colons
-INP.colons= value
-MOD.colons= ${INP.colons::::}
-EXP.colons= # empty
-
-.for test in ${TESTS}
-. if ${MOD.${test}} == ${EXP.${test}}
-. info ok ${test}
-. else
-. warning error in ${test}: expected "${EXP.${test}}", got "${MOD.${test}}"
-. endif
-.endfor
+INP= file.c file...
+MOD= ${INP:a\=b}
+EXP= # empty
+# expect+1: Unfinished modifier after "a\=b}", expecting "="
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
+
+INP= value
+MOD= ${INP:}
+EXP= value
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
+
+INP= value
+MOD= ${INP::::}
+EXP= :}
+# expect+1: Unknown modifier "::"
+.if ${MOD} != ${EXP}
+. warning expected "${EXP}", got "${MOD}"
+.endif
# Even in expressions based on an unnamed variable, there may be errors.
-# XXX: The error message should mention the variable name of the expression,
-# even though that name is empty in this case.
+# expect+1: Unknown modifier ":Z"
.if ${:Z}
. error
.else
@@ -184,12 +208,9 @@ EXP.colons= # empty
# variable name with quotes, leading to the rather confusing "Unfinished
# modifier for (',' missing)", having two spaces in a row.
#
-# XXX: The error message should report the filename:lineno.
+# expect+1: Unfinished modifier after "}", expecting ","
.if ${:S,}
. error
.else
. error
.endif
-
-all:
- @echo ok
diff --git a/contrib/bmake/unit-tests/varmod-gmtime.exp b/contrib/bmake/unit-tests/varmod-gmtime.exp
index fdc9a2170e2f..466895d48e6b 100644
--- a/contrib/bmake/unit-tests/varmod-gmtime.exp
+++ b/contrib/bmake/unit-tests/varmod-gmtime.exp
@@ -1,13 +1,13 @@
-make: "varmod-gmtime.mk" line 57: Invalid time value at "${:U1593536400}} != "mtime=11593536400}""
-make: "varmod-gmtime.mk" line 57: Malformed conditional (${%Y:L:gmtime=${:U1593536400}} != "mtime=11593536400}")
-make: "varmod-gmtime.mk" line 67: Invalid time value at "-1} != """
-make: "varmod-gmtime.mk" line 67: Malformed conditional (${:L:gmtime=-1} != "")
-make: "varmod-gmtime.mk" line 76: Invalid time value at " 1} != """
-make: "varmod-gmtime.mk" line 76: Malformed conditional (${:L:gmtime= 1} != "")
-make: "varmod-gmtime.mk" line 119: Invalid time value at "10000000000000000000000000000000} != """
-make: "varmod-gmtime.mk" line 119: Malformed conditional (${:L:gmtime=10000000000000000000000000000000} != "")
-make: "varmod-gmtime.mk" line 130: Invalid time value at "error} != """
-make: "varmod-gmtime.mk" line 130: Malformed conditional (${:L:gmtime=error} != "")
+make: varmod-gmtime.mk:60: Invalid time value "-1"
+ while evaluating "${:L:gmtime=-1} != """ with value ""
+make: varmod-gmtime.mk:70: Invalid time value " 1"
+ while evaluating "${:L:gmtime= 1} != """ with value ""
+make: varmod-gmtime.mk:117: Invalid time value "10000000000000000000000000000000"
+ while evaluating "${:L:gmtime=10000000000000000000000000000000} != """ with value ""
+make: varmod-gmtime.mk:129: Invalid time value "error"
+ while evaluating "${:L:gmtime=error} != """ with value ""
+make: varmod-gmtime.mk:139: Invalid time value "100000S,1970,bad,"
+ while evaluating variable "%Y" with value "%Y"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varmod-gmtime.mk b/contrib/bmake/unit-tests/varmod-gmtime.mk
index cb3d4e7eb241..610aa2d76fd1 100644
--- a/contrib/bmake/unit-tests/varmod-gmtime.mk
+++ b/contrib/bmake/unit-tests/varmod-gmtime.mk
@@ -1,7 +1,10 @@
-# $NetBSD: varmod-gmtime.mk,v 1.10 2021/01/19 05:26:34 rillig Exp $
+# $NetBSD: varmod-gmtime.mk,v 1.27 2025/01/11 20:54:45 rillig Exp $
#
# Tests for the :gmtime variable modifier, which formats a timestamp
# using strftime(3) in UTC.
+#
+# See also:
+# varmod-localtime.mk
.if ${TZ:Uundefined} != "undefined" # see unit-tests/Makefile
. error
@@ -41,20 +44,9 @@
.endif
-# As of 2020-08-16, it is not possible to pass the seconds via a
-# variable expression. This is because parsing of the :gmtime
-# modifier stops at the '$' and returns to ApplyModifiers.
-#
-# There, a colon would be skipped but not a dollar.
-# Parsing therefore continues at the '$' of the ${:U159...}, looking
-# for an ordinary variable modifier.
-#
-# At this point, the ${:U} is expanded and interpreted as a variable
-# modifier, which results in the error message "Unknown modifier '1'".
-#
-# If ApplyModifier_Gmtime were to pass its argument through
-# ParseModifierPart, this would work.
-.if ${%Y:L:gmtime=${:U1593536400}} != "mtime=11593536400}"
+# Before var.c 1.1050 from 2023-05-09, it was not possible to pass the
+# seconds via an expression.
+.if ${%Y:L:gmtime=${:U1593536400}} != "2020"
. error
.endif
@@ -64,6 +56,7 @@
# 1970. Going back 50 years in the past is not a practical use case for
# make. Therefore, since var.c 1.631, negative time stamps produce a
# parse error.
+# expect+1: Invalid time value "-1"
.if ${:L:gmtime=-1} != ""
. error
.else
@@ -73,8 +66,11 @@
# Spaces were allowed before var.c 1.631 from 2020-10-31 21:40:20, not
# because it would make sense but just as a side-effect from using strtoul.
+# expect+1: Invalid time value " 1"
.if ${:L:gmtime= 1} != ""
. error
+.else
+. error
.endif
@@ -115,7 +111,9 @@
# ULONG_MAX, which got converted to -1. This resulted in a time stamp of
# the second before 1970.
#
-# Since var.c 1.631, the overflow is detected and produces a parse error.
+# Since var.c 1.631 from 2020-10-31, the overflow is detected and produces a
+# parse error.
+# expect+1: Invalid time value "10000000000000000000000000000000"
.if ${:L:gmtime=10000000000000000000000000000000} != ""
. error
.else
@@ -127,11 +125,59 @@
# stopped after the '=', and the remaining string was parsed for more variable
# modifiers. Because of the unknown modifier 'e' from the 'error', the whole
# variable value was discarded and thus not printed.
+# expect+1: Invalid time value "error"
.if ${:L:gmtime=error} != ""
. error
.else
. error
.endif
+# Before var.c 1.1050 from 2023-05-09, the timestamp could be directly
+# followed by the next modifier, without a ':' separator. This was the same
+# bug as for the ':L' and ':P' modifiers.
+# expect+1: Invalid time value "100000S,1970,bad,"
+.if ${%Y:L:gmtime=100000S,1970,bad,} != "bad"
+. error
+.endif
+
+
+# Before var.c 1.1062 from 2023-08-19, ':gmtime' but not ':localtime' reported
+# wrong values for '%s', depending on the operating system and the timezone.
+export TZ=UTC
+.for t in ${%s:L:gmtime} ${%s:L:localtime}
+TIMESTAMPS+= $t
+.endfor
+export TZ=Europe/Berlin
+.for t in ${%s:L:gmtime} ${%s:L:localtime}
+TIMESTAMPS+= $t
+.endfor
+export TZ=UTC
+.for t in ${%s:L:gmtime} ${%s:L:localtime}
+TIMESTAMPS+= $t
+.endfor
+export TZ=America/Los_Angeles
+.for t in ${%s:L:gmtime} ${%s:L:localtime}
+TIMESTAMPS+= $t
+.endfor
+export TZ=UTC
+.for t in ${%s:L:gmtime} ${%s:L:localtime}
+TIMESTAMPS+= $t
+.endfor
+.for a b in ${TIMESTAMPS:[1]} ${TIMESTAMPS:@t@$t $t@} ${TIMESTAMPS:[-1]}
+. if $a > $b
+. warning timestamp $a > $b
+. endif
+.endfor
+
+
+.if ${year=%Y month=%m day=%d:L:gmtime=1459494000} != "year=2016 month=04 day=01"
+. error
+.endif
+# Slightly contorted syntax to convert a UTC timestamp from an expression to a
+# formatted timestamp.
+.if ${%Y%m%d:L:${gmtime=${:U1459494000}:L}} != "20160401"
+. error
+.endif
+
all:
diff --git a/contrib/bmake/unit-tests/varmod-hash.exp b/contrib/bmake/unit-tests/varmod-hash.exp
index 1286b456c6c2..1abc6bc92a9f 100644
--- a/contrib/bmake/unit-tests/varmod-hash.exp
+++ b/contrib/bmake/unit-tests/varmod-hash.exp
@@ -1,9 +1,15 @@
-make: Unknown modifier "has"
-
+make: Unknown modifier ":has"
+ while evaluating variable "12345" with value "12345"
+ in command "@echo ${12345:L:has} # modifier name too short"
+ in target "step-1"
26bb0f5f
12345
-make: Unknown modifier "hasX"
-
-make: Unknown modifier "hashed"
-
-exit status 0
+make: Unknown modifier ":hasX"
+ while evaluating variable "12345" with value "12345"
+ in command "@echo ${12345:L:hasX} # misspelled"
+ in target "step-4"
+make: Unknown modifier ":hashed"
+ while evaluating variable "12345" with value "12345"
+ in command "@echo ${12345:L:hashed} # modifier name too long"
+ in target "step-5"
+exit status 2
diff --git a/contrib/bmake/unit-tests/varmod-hash.mk b/contrib/bmake/unit-tests/varmod-hash.mk
index 5407e8299f9e..7b34b74226f2 100644
--- a/contrib/bmake/unit-tests/varmod-hash.mk
+++ b/contrib/bmake/unit-tests/varmod-hash.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-hash.mk,v 1.5 2020/09/04 06:54:07 rillig Exp $
+# $NetBSD: varmod-hash.mk,v 1.6 2024/07/20 11:05:12 rillig Exp $
#
# Tests for the :hash variable modifier, which computes a 32-bit hash from
# the value of the expression.
@@ -56,9 +56,14 @@ VECTORS+= de41416c abcdefghijklmnopqrstuvwxyz
. endif
.endfor
-all:
+all: step-{1,2,3,4,5}
+step-1:
@echo ${12345:L:has} # modifier name too short
+step-2:
@echo ${12345:L:hash} # ok
+step-3:
@echo ${12345:L:hash=SHA-256} # :hash does not accept '='
+step-4:
@echo ${12345:L:hasX} # misspelled
+step-5:
@echo ${12345:L:hashed} # modifier name too long
diff --git a/contrib/bmake/unit-tests/varmod-head.exp b/contrib/bmake/unit-tests/varmod-head.exp
index 651844439f5f..39a9383953dd 100644
--- a/contrib/bmake/unit-tests/varmod-head.exp
+++ b/contrib/bmake/unit-tests/varmod-head.exp
@@ -1,11 +1 @@
-head (dirname) of 'a/b/c' is 'a/b'
-head (dirname) of 'def' is '.'
-head (dirname) of 'a.b.c' is '.'
-head (dirname) of 'a.b/c' is 'a.b'
-head (dirname) of 'a' is '.'
-head (dirname) of 'a.a' is '.'
-head (dirname) of '.gitignore' is '.'
-head (dirname) of 'a' is '.'
-head (dirname) of 'a.a' is '.'
-head (dirname) of 'trailing/' is 'trailing'
exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-head.mk b/contrib/bmake/unit-tests/varmod-head.mk
index 66347b4bce61..03d338d42742 100644
--- a/contrib/bmake/unit-tests/varmod-head.mk
+++ b/contrib/bmake/unit-tests/varmod-head.mk
@@ -1,9 +1,70 @@
-# $NetBSD: varmod-head.mk,v 1.4 2020/12/20 22:57:40 rillig Exp $
+# $NetBSD: varmod-head.mk,v 1.6 2024/06/01 18:44:05 rillig Exp $
#
# Tests for the :H variable modifier, which returns the dirname of
# each of the words in the variable value.
-all:
-.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a trailing/
- @echo "head (dirname) of '"${path:Q}"' is '"${path:H:Q}"'"
-.endfor
+.if ${:U a/b/c :H} != "a/b"
+. error
+.endif
+
+.if ${:U def :H} != "."
+. error
+.endif
+
+.if ${:U a.b.c :H} != "."
+. error
+.endif
+
+.if ${:U a.b/c :H} != "a.b"
+. error
+.endif
+
+.if ${:U a :H} != "."
+. error
+.endif
+
+.if ${:U a.a :H} != "."
+. error
+.endif
+
+.if ${:U .gitignore :H} != "."
+. error
+.endif
+
+.if ${:U trailing/ :H} != "trailing"
+. error
+.endif
+
+.if ${:U /abs/dir/file :H} != "/abs/dir"
+. error
+.endif
+
+.if ${:U rel/dir/file :H} != "rel/dir"
+. error
+.endif
+
+# The head of "/" was an empty string before 2020.07.20.14.50.41, leading to
+# the output "before after", with two spaces. Since 2020.07.20.14.50.41, the
+# output is "before after", discarding the empty word.
+.if ${:U before/ / after/ :H} == "before after"
+# OK
+.elif ${:U before/ / after/ :H} == "before after"
+# No '.info' to keep the file compatible with old make versions.
+_!= echo "The modifier ':H' generates an empty word." 1>&2; echo
+.else
+. error
+.endif
+
+# An empty list is split into a single empty word.
+# The dirname of this empty word is ".".
+.if ${:U :H} != "."
+. error
+.endif
+
+# If the ':H' is not directly followed by a delimiting ':' or '}', the
+# ':from=to' modifier is tried as a fallback.
+.if ${:U Head :Head=replaced} != "replaced"
+. error
+.endif
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/varmod-ifelse.exp b/contrib/bmake/unit-tests/varmod-ifelse.exp
index 7134c71b8d39..bf642c86fc8c 100644
--- a/contrib/bmake/unit-tests/varmod-ifelse.exp
+++ b/contrib/bmake/unit-tests/varmod-ifelse.exp
@@ -1,32 +1,70 @@
-make: Bad conditional expression 'variable expression == "literal"' in 'variable expression == "literal"?bad:bad'
-make: "varmod-ifelse.mk" line 27: Malformed conditional (${${:Uvariable expression} == "literal":?bad:bad})
-make: Bad conditional expression ' == ""' in ' == ""?bad-assign:bad-assign'
-make: Bad conditional expression ' == ""' in ' == ""?bad-cond:bad-cond'
-make: "varmod-ifelse.mk" line 44: Malformed conditional (${${UNDEF} == "":?bad-cond:bad-cond})
-make: Bad conditional expression '1 == == 2' in '1 == == 2?yes:no'
-make: "varmod-ifelse.mk" line 66: Malformed conditional (${1 == == 2:?yes:no} != "")
+make: varmod-ifelse.mk:28: Bad condition
+ while evaluating condition "bare words == "literal""
+make: varmod-ifelse.mk:39: Bad condition
+ while evaluating condition " == """
+make: varmod-ifelse.mk:47: Bad condition
+ while evaluating condition " == """
+make: varmod-ifelse.mk:70: Bad condition
+ while evaluating condition "1 == == 2"
CondParser_Eval: "${1 == == 2:?yes:no}" != ""
CondParser_Eval: 1 == == 2
Comparing 1.000000 == 0.000000
-make: Bad conditional expression '1 == == 2' in '1 == == 2?yes:no'
+make: varmod-ifelse.mk:94: Bad condition
+ while evaluating condition "1 == == 2"
Comparing "" != ""
-make: "varmod-ifelse.mk" line 92: warning: Oops, the parse error should have been propagated.
-CondParser_Eval: ${ ${:U\$}{VAR} == value :?ok:bad} != "ok"
-CondParser_Eval: ${VAR} == value
+CondParser_Eval: ${ ${:U\$}{VAR} == value:?ok:bad} != "ok"
+CondParser_Eval: ${VAR} == value
Comparing "value" == "value"
Comparing "ok" != "ok"
-make: "varmod-ifelse.mk" line 153: no.
-make: "varmod-ifelse.mk" line 154: String comparison operator must be either == or !=
-make: Bad conditional expression 'string == "literal" || no >= 10' in 'string == "literal" || no >= 10?yes:no'
-make: "varmod-ifelse.mk" line 154: .
-make: Bad conditional expression 'string == "literal" && >= 10' in 'string == "literal" && >= 10?yes:no'
-make: "varmod-ifelse.mk" line 159: .
-make: Bad conditional expression 'string == "literal" || >= 10' in 'string == "literal" || >= 10?yes:no'
-make: "varmod-ifelse.mk" line 160: .
-make: "varmod-ifelse.mk" line 167: true
-make: "varmod-ifelse.mk" line 169: false
-make: Bad conditional expression ' ' in ' ?true:false'
-make: "varmod-ifelse.mk" line 171:
+make: varmod-ifelse.mk:159: no.
+make: varmod-ifelse.mk:162: Comparison with ">=" requires both operands "no" and "10" to be numeric
+ while evaluating condition "string == "literal" || no >= 10"
+make: varmod-ifelse.mk:162: .
+make: varmod-ifelse.mk:169: Bad condition
+ while evaluating condition "string == "literal" && >= 10"
+make: varmod-ifelse.mk:169: .
+make: varmod-ifelse.mk:172: Bad condition
+ while evaluating condition "string == "literal" || >= 10"
+make: varmod-ifelse.mk:172: .
+make: varmod-ifelse.mk:180: <true>
+make: varmod-ifelse.mk:183: <false>
+make: varmod-ifelse.mk:187: Bad condition
+ while evaluating condition " "
+make: varmod-ifelse.mk:187: <>
+CondParser_Eval: 0 && ${1:?${:Uthen0:S,}},,}:${:Uelse0:S,}},,}} != "not evaluated"
+CondParser_Eval: 1 && ${0:?${:Uthen1:S,}},,}:${:Uelse1:S,}},,}} != "else1"
+CondParser_Eval: 0
+Comparing "else1" != "else1"
+CondParser_Eval: 2 && ${1:?${:Uthen2:S,}},,}:${:Uelse2:S,}},,}} != "then2"
+CondParser_Eval: 1
+Comparing "then2" != "then2"
+CondParser_Eval: ${DELAYED} == "one"
+Comparing "two" == "one"
+make: varmod-ifelse.mk:283: no
+CondParser_Eval: ${DELAYED} == "two"
+Comparing "two" == "two"
+make: varmod-ifelse.mk:285: yes
+CondParser_Eval: ${DELAYED} == "one"
+Comparing "two" == "one"
+make: varmod-ifelse.mk:288: no
+CondParser_Eval: ${DELAYED} == "two"
+Comparing "two" == "two"
+make: varmod-ifelse.mk:291: yes
+make: varmod-ifelse.mk:313: Unknown modifier ":X-then"
+ while evaluating "${:X-then}:${:X-else}}" with value ""
+ while evaluating then-branch of condition "1"
+make: varmod-ifelse.mk:313: Unknown modifier ":X-else"
+ while parsing "${:X-else}}"
+ while evaluating else-branch of condition "1"
+make: varmod-ifelse.mk:321: Bad condition
+ while evaluating condition " < 0 "
+make: varmod-ifelse.mk:321: Unknown modifier ":Z1"
+ while parsing "${:Z1}:${:Z2}}>"
+ while evaluating then-branch of condition " < 0 "
+make: varmod-ifelse.mk:321: Unknown modifier ":Z2"
+ while parsing "${:Z2}}>"
+ while evaluating else-branch of condition " < 0 "
+make: varmod-ifelse.mk:321: <>
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varmod-ifelse.mk b/contrib/bmake/unit-tests/varmod-ifelse.mk
index 37e8f620d883..fcd483d0c497 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.18 2022/01/15 20:16:55 rillig Exp $
+# $NetBSD: varmod-ifelse.mk,v 1.41 2025/06/29 11:27:21 rillig Exp $
#
# Tests for the ${cond:?then:else} variable modifier, which evaluates either
# the then-expression or the else-expression, depending on the condition.
@@ -13,18 +13,19 @@
# The variable name of the expression is expanded and then taken as the
# condition. In the below example it becomes:
#
-# variable expression == "literal"
+# bare words == "literal"
#
# This confuses the parser, which expects an operator instead of the bare
# word "expression". If the name were expanded lazily, everything would be
# fine since the condition would be:
#
-# ${:Uvariable expression} == "literal"
+# ${:Ubare words} == "literal"
#
# Evaluating the variable name lazily would require additional code in
# Var_Parse and ParseVarname, it would be more useful and predictable
# though.
-.if ${${:Uvariable expression} == "literal":?bad:bad}
+# expect+1: Bad condition
+.if ${${:Ubare words} == "literal":?bad:bad}
. error
.else
. error
@@ -34,13 +35,15 @@
# Because of the early expansion, the whole condition evaluates to
# ' == ""' though, which cannot be parsed because the left-hand side looks
# empty.
+# expect+1: Bad condition
COND:= ${${UNDEF} == "":?bad-assign:bad-assign}
-# In a condition, undefined variables generate a "Malformed conditional"
-# error. That error message is wrong though. In lint mode, the correct
-# "Undefined variable" error message is generated.
-# The difference to the ':=' variable assignment is the additional
-# "Malformed conditional" error message.
+# In a conditional directive, undefined variables are reported as such. In a
+# ':?' modifier, though, the "variable name" is expanded first, and in that
+# context, an undefined expression is not an error. The "variable name" then
+# becomes the condition, in this case ' == ""', which is malformed because the
+# left-hand side looks empty.
+# expect+1: Bad condition
.if ${${UNDEF} == "":?bad-cond:bad-cond}
. error
.else
@@ -59,24 +62,25 @@ COND:= ${${UNDEF} == "":?bad-assign:bad-assign}
# This line generates 2 error messages. The first comes from evaluating the
# malformed conditional "1 == == 2", which is reported as "Bad conditional
-# expression" by ApplyModifier_IfElse. The variable expression containing that
+# expression" by ApplyModifier_IfElse. The expression containing that
# conditional therefore returns a parse error from Var_Parse, and this parse
# error propagates to CondEvalExpression, where the "Malformed conditional"
# comes from.
+# expect+1: Bad condition
.if ${1 == == 2:?yes:no} != ""
. error
.else
. error
.endif
-# If the "Bad conditional expression" appears in a quoted string literal, the
+# If the "Bad condition" appears in a quoted string literal, the
# error message "Malformed conditional" is not printed, leaving only the "Bad
-# conditional expression".
+# condition".
#
# XXX: The left-hand side is enclosed in quotes. This results in Var_Parse
-# being called without VARE_UNDEFERR. When ApplyModifier_IfElse
+# being called without VARE_EVAL_DEFINED. When ApplyModifier_IfElse
# returns AMR_CLEANUP as result, Var_Parse returns varUndefined since the
-# value of the variable expression is still undefined. CondParser_String is
+# value of the expression is still undefined. CondParser_String is
# then supposed to do proper error handling, but since varUndefined is local
# to var.c, it cannot distinguish this return value from an ordinary empty
# string. The left-hand side of the comparison is therefore just an empty
@@ -86,18 +90,19 @@ COND:= ${${UNDEF} == "":?bad-assign:bad-assign}
# condition should be detected as being malformed before any comparison is
# done since there is no well-formed comparison in the condition at all.
.MAKEFLAGS: -dc
+# expect+1: Bad condition
.if "${1 == == 2:?yes:no}" != ""
. error
.else
-. warning Oops, the parse error should have been propagated.
+. error
.endif
.MAKEFLAGS: -d0
-# As of 2020-12-10, the variable "name" is first expanded, and the result of
-# this expansion is then taken as the condition. To force the variable
+# As of 2020-12-10, the variable "VAR" is first expanded, and the result of
+# this expansion is then taken as the condition. To force the
# expression in the condition to be evaluated at exactly the right point,
# the '$' of the intended '${VAR}' escapes from the parser in form of the
-# expression ${:U\$}. Because of this escaping, the variable "name" and thus
+# expression ${:U\$}. Because of this escaping, the variable "VAR" and thus
# the condition ends up as "${VAR} == value", just as intended.
#
# This hack does not work for variables from .for loops since these are
@@ -106,7 +111,7 @@ COND:= ${${UNDEF} == "":?bad-assign:bad-assign}
# from the parser of the .for loop body. See ForLoop_SubstVarLong.
.MAKEFLAGS: -dc
VAR= value
-.if ${ ${:U\$}{VAR} == value :?ok:bad} != "ok"
+.if ${ ${:U\$}{VAR} == value:?ok:bad} != "ok"
. error
.endif
.MAKEFLAGS: -d0
@@ -133,7 +138,7 @@ VAR= value
# When parsing such an expression, the parser used to be strict. It first
# evaluated the left-hand side of the operator '&&' and then started parsing
# the right-hand side 'no >= 10'. The word 'no' is obviously a string
-# literal, not enclosed in quotes, which is ok, even on the left-hand side of
+# literal, not enclosed in quotes, which is OK, even on the left-hand side of
# the comparison operator, but only because this is a condition in the
# modifier ':?'. In an ordinary directive '.if', this would be a parse error.
# For strings, only the comparison operators '==' and '!=' are defined,
@@ -150,13 +155,20 @@ VAR= value
# instead of just saying that the whole condition is bad.
STRING= string
NUMBER= no # not really a number
+# expect+1: no.
.info ${${STRING} == "literal" && ${NUMBER} >= 10:?yes:no}.
+# expect+2: Comparison with ">=" requires both operands "no" and "10" to be numeric
+# expect+1: .
.info ${${STRING} == "literal" || ${NUMBER} >= 10:?yes:no}.
# The following situation occasionally occurs with MKINET6 or similar
# variables.
NUMBER= # empty, not really a number either
+# expect+2: Bad condition
+# expect+1: .
.info ${${STRING} == "literal" && ${NUMBER} >= 10:?yes:no}.
+# expect+2: Bad condition
+# expect+1: .
.info ${${STRING} == "literal" || ${NUMBER} >= 10:?yes:no}.
# CondParser_LeafToken handles [0-9-+] specially, treating them as a number.
@@ -164,8 +176,146 @@ PLUS= +
ASTERISK= *
EMPTY= # empty
# "true" since "+" is not the empty string.
-.info ${${PLUS} :?true:false}
+# expect+1: <true>
+.info <${${PLUS} :?true:false}>
# "false" since the variable named "*" is not defined.
-.info ${${ASTERISK} :?true:false}
+# expect+1: <false>
+.info <${${ASTERISK} :?true:false}>
# syntax error since the condition is completely blank.
-.info ${${EMPTY} :?true:false}
+# expect+2: Bad condition
+# expect+1: <>
+.info <${${EMPTY} :?true:false}>
+
+
+# Since the condition of the '?:' modifier is expanded before being parsed and
+# evaluated, it is common practice to enclose expressions in quotes, to avoid
+# producing syntactically invalid conditions such as ' == value'. This only
+# works if the expanded values neither contain quotes nor backslashes. For
+# strings containing quotes or backslashes, the '?:' modifier should not be
+# used.
+PRIMES= 2 3 5 7 11
+.if ${1 2 3 4 5:L:@n@$n:${ ("${PRIMES:M$n}" != "") :?prime:not_prime}@} != \
+ "1:not_prime 2:prime 3:prime 4:not_prime 5:prime"
+. error
+.endif
+
+# When parsing the modifier ':?', there are 3 possible cases:
+#
+# 1. The whole expression is only parsed.
+# 2. The expression is parsed and the 'then' branch is evaluated.
+# 3. The expression is parsed and the 'else' branch is evaluated.
+#
+# In all of these cases, the expression must be parsed in the same way,
+# especially when one of the branches contains unbalanced '{}' braces.
+#
+# At 2020-01-01, the expressions from the 'then' and 'else' branches were
+# parsed differently, depending on whether the branch was taken or not. When
+# the branch was taken, the parser recognized that in the modifier ':S,}},,',
+# the '}}' were ordinary characters. When the branch was not taken, the
+# parser only counted balanced '{' and '}', ignoring any escaping or other
+# changes in the interpretation.
+#
+# In var.c 1.285 from 2020-07-20, the parsing of the expressions changed so
+# that in both cases the expression is parsed in the same way, taking the
+# unbalanced braces in the ':S' modifiers into account. This change was not
+# on purpose, the commit message mentioned 'has the same effect', which was a
+# wrong assumption.
+#
+# In var.c 1.323 from 2020-07-26, the unintended fix from var.c 1.285 was
+# reverted, still not knowing about the difference between regular parsing and
+# balanced-mode parsing.
+#
+# In var.c 1.1028 from 2022-08-08, there was another attempt at fixing this
+# inconsistency in parsing, but since that broke parsing of the modifier ':@',
+# it was reverted in var.c 1.1029 from 2022-08-23.
+#
+# In var.c 1.1047 from 2023-02-18, the inconsistency in parsing was finally
+# fixed. The modifier ':@' now parses the body in balanced mode, while
+# everywhere else the modifier parts have their subexpressions parsed in the
+# same way, no matter whether they are evaluated or not.
+#
+# The modifiers ':@' and ':?' are similar in that they conceptually contain
+# text to be evaluated later or conditionally, still they parse that text
+# differently. The crucial difference is that the body of the modifier ':@'
+# is always parsed using balanced mode. The modifier ':?', on the other hand,
+# must parse both of its branches in the same way, no matter whether they are
+# evaluated or not. Since balanced mode and standard mode are incompatible,
+# it's impossible to use balanced mode in the modifier ':?'.
+.MAKEFLAGS: -dc
+.if 0 && ${1:?${:Uthen0:S,}},,}:${:Uelse0:S,}},,}} != "not evaluated"
+# At 2020-01-07, the expression evaluated to 'then0,,}}', even though it was
+# irrelevant as the '0' had already been evaluated to 'false'.
+. error
+.endif
+.if 1 && ${0:?${:Uthen1:S,}},,}:${:Uelse1:S,}},,}} != "else1"
+. error
+.endif
+.if 2 && ${1:?${:Uthen2:S,}},,}:${:Uelse2:S,}},,}} != "then2"
+# At 2020-01-07, the whole expression evaluated to 'then2,,}}' instead of the
+# expected 'then2'. The 'then' branch of the ':?' modifier was parsed
+# normally, parsing and evaluating the ':S' modifier, thereby treating the
+# '}}' as ordinary characters and resulting in 'then2'. The 'else' branch was
+# parsed in balanced mode, ignoring that the inner '}}' were ordinary
+# characters. The '}}' were thus interpreted as the end of the 'else' branch
+# and the whole expression. This left the trailing ',,}}', which together
+# with the 'then2' formed the result 'then2,,}}'.
+. error
+.endif
+
+
+# Since the condition is taken from the variable name of the expression, not
+# from its value, it is evaluated early. It is possible though to construct
+# conditions that are evaluated lazily, at exactly the right point. There is
+# no way to escape a '$' directly in the variable name, but there are
+# alternative ways to bring a '$' into the condition.
+#
+# In an indirect condition using the ':U' modifier, each '$', ':' and
+# '}' must be escaped as '\$', '\:' and '\}', respectively, but '{' must
+# not be escaped.
+#
+# In an indirect condition using a separate variable, each '$' must be
+# escaped as '$$'.
+#
+# These two forms allow the variables to contain arbitrary characters, as the
+# condition parser does not see them.
+DELAYED= two
+# expect+1: no
+.info ${ ${:U \${DELAYED\} == "one"}:?yes:no}
+# expect+1: yes
+.info ${ ${:U \${DELAYED\} == "two"}:?yes:no}
+INDIRECT_COND1= $${DELAYED} == "one"
+# expect+1: no
+.info ${ ${INDIRECT_COND1}:?yes:no}
+INDIRECT_COND2= $${DELAYED} == "two"
+# expect+1: yes
+.info ${ ${INDIRECT_COND2}:?yes:no}
+
+
+.MAKEFLAGS: -d0
+
+
+# In the modifier parts for the 'then' and 'else' branches, subexpressions are
+# parsed by inspecting the actual modifiers. In 2008, 2015, 2020, 2022 and
+# 2023, the exact parsing algorithm switched a few times, counting balanced
+# braces instead of proper subexpressions, which meant that unbalanced braces
+# were parsed differently, depending on whether the branch was active or not.
+BRACES= }}}
+NO= ${0:?${BRACES:S,}}},yes,}:${BRACES:S,}}},no,}}
+YES= ${1:?${BRACES:S,}}},yes,}:${BRACES:S,}}},no,}}
+BOTH= <${YES}> <${NO}>
+.if ${BOTH} != "<yes> <no>"
+. error
+.endif
+
+
+# expect+2: Unknown modifier ":X-then"
+# expect+1: Unknown modifier ":X-else"
+.if ${1:?${:X-then}:${:X-else}}
+.endif
+
+
+# expect+4: Bad condition
+# expect+3: Unknown modifier ":Z1"
+# expect+2: Unknown modifier ":Z2"
+# expect+1: <>
+.info <${ < 0 :?${:Z1}:${:Z2}}>
diff --git a/contrib/bmake/unit-tests/varmod-indirect.exp b/contrib/bmake/unit-tests/varmod-indirect.exp
index 46fa1af7a8cb..33a800c1ef64 100644
--- a/contrib/bmake/unit-tests/varmod-indirect.exp
+++ b/contrib/bmake/unit-tests/varmod-indirect.exp
@@ -1,20 +1,21 @@
-make: "varmod-indirect.mk" line 19: Unknown modifier "${"
-make: "varmod-indirect.mk" line 52: Unknown modifier "${"
-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
-make: "varmod-indirect.mk" line 146: after
-make: "varmod-indirect.mk" line 152: before
-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
-Parsing line 166: _:= before ${UNDEF} after
+make: varmod-indirect.mk:19: Unknown modifier ":${"
+ while evaluating variable "value" with value "value"
+make: varmod-indirect.mk:52: Unknown modifier ":${"
+ while evaluating variable "value" with value "value"
+make: varmod-indirect.mk:140: before
+make: varmod-indirect.mk:140: after
+make: varmod-indirect.mk:148: before
+make: varmod-indirect.mk:148: after
+make: varmod-indirect.mk:156: before
+make: varmod-indirect.mk:156: after
+make: varmod-indirect.mk:161: Unknown modifier ":Z"
+ while evaluating indirect modifiers "Z"
+ while evaluating variable "UNDEF" with value ""
+Parsing varmod-indirect.mk:171: _:= before ${UNDEF} after
Global: _ = # (empty)
Var_Parse: ${UNDEF} after (eval-keep-dollar-and-undefined)
Global: _ = before ${UNDEF} after
-Parsing line 169: _:= before ${UNDEF:${:US,a,a,}} after
+Parsing varmod-indirect.mk:174: _:= 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 +24,19 @@ 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
-Parsing line 179: _:= before ${UNDEF:${:U}} after
+Parsing varmod-indirect.mk:184: _:= before ${UNDEF:${:U}} after
Var_Parse: ${UNDEF:${:U}} after (eval-keep-dollar-and-undefined)
Indirect modifier "" from "${:U}"
Global: _ = before ${UNDEF:} after
-Parsing line 184: _:= before ${UNDEF:${:UZ}} after
+Parsing varmod-indirect.mk:190: _:= 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)
+make: varmod-indirect.mk:190: Unknown modifier ":Z"
+ while evaluating indirect modifiers "Z"
+ while evaluating variable "UNDEF" with value ""
Global: _ = before ${UNDEF:Z} after
-Parsing line 186: .MAKEFLAGS: -d0
+Parsing varmod-indirect.mk:192: .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 082efb035c74..88d7db300704 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.11 2022/01/15 12:35:18 rillig Exp $
+# $NetBSD: varmod-indirect.mk,v 1.24 2025/03/30 16:43:10 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
@@ -11,11 +11,11 @@
# To apply a modifier indirectly via another variable, the whole
-# modifier must be put into a single variable expression.
+# modifier must be put into a single expression.
# The following expression generates a parse error since its indirect
-# modifier contains more than a sole variable expression.
+# modifier contains more than a sole expression.
#
-# expect+1: Unknown modifier "${"
+# expect+1: Unknown modifier ":${"
.if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}"
. warning unexpected
.endif
@@ -44,15 +44,13 @@
# If an expression for an indirect modifier evaluates to anything else than an
# empty string and is neither followed by a ':' nor '}', this produces a parse
-# error. Because of this parse error, this feature cannot be used reasonably
+# error. Due to this parse error, this construct cannot be used reasonably
# in practice.
#
-# expect+2: 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 $\
- error rather than returning the unparsed portion of the $\
- expression.
+.if ${value:L:${:UM*}S,value,replaced,} == "anything"
+. error
.else
. error
.endif
@@ -70,20 +68,20 @@
.endif
-# The nested variable expression expands to "tu", and this is interpreted as
+# The nested expression expands to "tu", and this is interpreted as
# a variable modifier for the value "Upper", resulting in "UPPER".
.if ${Upper:L:${:Utu}} != "UPPER"
. error
.endif
-# The nested variable expression expands to "tl", and this is interpreted as
+# The nested expression expands to "tl", and this is interpreted as
# a variable modifier for the value "Lower", resulting in "lower".
.if ${Lower:L:${:Utl}} != "lower"
. error
.endif
-# The nested variable expression is ${1 != 1:?Z:tl}, consisting of the
+# The nested expression is ${1 != 1:?Z:tl}, consisting of the
# condition "1 != 1", the then-branch "Z" and the else-branch "tl". Since
# the condition evaluates to false, the then-branch is ignored (it would
# have been an unknown modifier anyway) and the ":tl" modifier is applied.
@@ -132,29 +130,36 @@ M_NoPrimes= ${PRIMES:${M_ListToSkip}}
.MAKEFLAGS: -d0
-# In contrast to the .if conditions, the .for loop allows undefined variable
+# In contrast to the .if conditions, the .for loop allows undefined
# expressions. These expressions expand to empty strings.
# An undefined expression without any modifiers expands to an empty string.
.for var in before ${UNDEF} after
+# expect+2: before
+# expect+1: after
. info ${var}
.endfor
# An undefined expression with only modifiers that keep the expression
# undefined expands to an empty string.
.for var in before ${UNDEF:${:US,a,a,}} after
+# expect+2: before
+# expect+1: after
. info ${var}
.endfor
# Even in an indirect modifier based on an undefined variable, the value of
# the expression in Var_Parse is a simple empty string.
.for var in before ${UNDEF:${:U}} after
+# expect+2: before
+# expect+1: after
. info ${var}
.endfor
# An error in an indirect modifier.
+# expect+1: Unknown modifier ":Z"
.for var in before ${UNDEF:${:UZ}} after
-. info ${var}
+. error
.endfor
@@ -162,10 +167,10 @@ M_NoPrimes= ${PRIMES:${M_ListToSkip}}
# a variable assignment using ':='.
.MAKEFLAGS: -dpv
-# The undefined variable expression is kept as-is.
+# The undefined expression is kept as-is.
_:= before ${UNDEF} after
-# The undefined variable expression is kept as-is.
+# The undefined expression is kept as-is.
_:= before ${UNDEF:${:US,a,a,}} after
# XXX: The subexpression ${:U} is fully defined, therefore it is expanded.
@@ -179,8 +184,9 @@ _:= before ${UNDEF:${:US,a,a,}} after
_:= before ${UNDEF:${:U}} after
# XXX: This expands to ${UNDEF:Z}, which will behave differently if the
-# variable '_' is used in a context where the variable expression ${_} is
+# variable '_' is used in a context where the expression ${_} is
# parsed but not evaluated.
+# expect+1: Unknown modifier ":Z"
_:= before ${UNDEF:${:UZ}} after
.MAKEFLAGS: -d0
@@ -190,7 +196,7 @@ _:= before ${UNDEF:${:UZ}} after
# When evaluating indirect modifiers, these modifiers may expand to ':tW',
# which modifies the interpretation of the expression value. This modified
# interpretation only lasts until the end of the indirect modifier, it does
-# not influence the outer variable expression.
+# not influence the outer expression.
.if ${1 2 3:L:tW:[#]} != 1 # direct :tW applies to the :[#]
. error
.endif
@@ -202,7 +208,7 @@ _:= before ${UNDEF:${:UZ}} after
# When evaluating indirect modifiers, these modifiers may expand to ':ts*',
# which modifies the interpretation of the expression value. This modified
# interpretation only lasts until the end of the indirect modifier, it does
-# not influence the outer variable expression.
+# not influence the outer expression.
#
# In this first expression, the direct ':ts*' has no effect since ':U' does not
# treat the expression value as a list of words but as a single word. It has
@@ -244,4 +250,28 @@ _:= before ${UNDEF:${:UZ}} after
. error
.endif
-all:
+
+# In parse-only mode, the indirect modifiers must not be evaluated.
+#
+# Before var.c 1.1098 from 2024-02-04, the expression for an indirect modifier
+# was partially evaluated (only the variable value, without applying any
+# modifiers) and then interpreted as modifiers to the main expression.
+#
+# The expression ${:UZ} starts with the value "", and in parse-only mode, the
+# modifier ':UZ' does not modify the expression value. This results in an
+# empty string for the indirect modifiers, generating no warning.
+.if 0 && ${VAR:${:UZ}}
+.endif
+# The expression ${M_invalid} starts with the value "Z", which is an unknown
+# modifier. Trying to apply this unknown modifier generated a warning.
+M_invalid= Z
+.if 0 && ${VAR:${M_invalid}}
+.endif
+# The ':S' modifier does not change the expression value in parse-only mode,
+# keeping the "Z", which is then skipped in parse-only mode.
+.if 0 && ${VAR:${M_invalid:S,^,N*,:ts:}}
+.endif
+# The ':@' modifier does not change the expression value in parse-only mode,
+# keeping the "Z", which is then skipped in parse-only mode.
+.if 0 && ${VAR:${M_invalid:@m@N*$m@:ts:}}
+.endif
diff --git a/contrib/bmake/unit-tests/varmod-l-name-to-value.mk b/contrib/bmake/unit-tests/varmod-l-name-to-value.mk
index 354622cf098b..e87e68967544 100644
--- a/contrib/bmake/unit-tests/varmod-l-name-to-value.mk
+++ b/contrib/bmake/unit-tests/varmod-l-name-to-value.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-l-name-to-value.mk,v 1.7 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: varmod-l-name-to-value.mk,v 1.8 2023/11/19 21:47:52 rillig Exp $
#
# Tests for the :L modifier, which returns the variable name as the new value.
@@ -28,7 +28,7 @@
.endif
# Between 2020-09-22 (var.c 1.527) and 2020-09-30 (var.c 1.553), there was
-# a bug in the evaluation of variable expressions. Indirect modifiers like
+# a bug in the evaluation of expressions. Indirect modifiers like
# the below :L did not update the definedness of the enclosing expression.
# This resulted in a wrong "Malformed conditional".
.if ${value:${:UL}} == ""
diff --git a/contrib/bmake/unit-tests/varmod-localtime.exp b/contrib/bmake/unit-tests/varmod-localtime.exp
index 494f160b766e..a92b1d0f29a3 100644
--- a/contrib/bmake/unit-tests/varmod-localtime.exp
+++ b/contrib/bmake/unit-tests/varmod-localtime.exp
@@ -1,13 +1,13 @@
-make: "varmod-localtime.mk" line 57: Invalid time value at "${:U1593536400}} != "mtime=11593536400}""
-make: "varmod-localtime.mk" line 57: Malformed conditional (${%Y:L:localtime=${:U1593536400}} != "mtime=11593536400}")
-make: "varmod-localtime.mk" line 67: Invalid time value at "-1} != """
-make: "varmod-localtime.mk" line 67: Malformed conditional (${:L:localtime=-1} != "")
-make: "varmod-localtime.mk" line 76: Invalid time value at " 1} != """
-make: "varmod-localtime.mk" line 76: Malformed conditional (${:L:localtime= 1} != "")
-make: "varmod-localtime.mk" line 119: Invalid time value at "10000000000000000000000000000000} != """
-make: "varmod-localtime.mk" line 119: Malformed conditional (${:L:localtime=10000000000000000000000000000000} != "")
-make: "varmod-localtime.mk" line 130: Invalid time value at "error} != """
-make: "varmod-localtime.mk" line 130: Malformed conditional (${:L:localtime=error} != "")
+make: varmod-localtime.mk:60: Invalid time value "-1"
+ while evaluating "${:L:localtime=-1} != """ with value ""
+make: varmod-localtime.mk:70: Invalid time value " 1"
+ while evaluating "${:L:localtime= 1} != """ with value ""
+make: varmod-localtime.mk:117: Invalid time value "10000000000000000000000000000000"
+ while evaluating "${:L:localtime=10000000000000000000000000000000} != """ with value ""
+make: varmod-localtime.mk:129: Invalid time value "error"
+ while evaluating "${:L:localtime=error} != """ with value ""
+make: varmod-localtime.mk:139: Invalid time value "100000S,1970,bad,"
+ while evaluating variable "%Y" with value "%Y"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varmod-localtime.mk b/contrib/bmake/unit-tests/varmod-localtime.mk
index ffa09a0bc5fc..41c99c86df6e 100644
--- a/contrib/bmake/unit-tests/varmod-localtime.mk
+++ b/contrib/bmake/unit-tests/varmod-localtime.mk
@@ -1,7 +1,10 @@
-# $NetBSD: varmod-localtime.mk,v 1.8 2021/01/19 05:26:34 rillig Exp $
+# $NetBSD: varmod-localtime.mk,v 1.20 2025/01/11 20:54:46 rillig Exp $
#
# Tests for the :localtime variable modifier, which formats a timestamp
# using strftime(3) in local time.
+#
+# See also:
+# varmod-gmtime.mk
.if ${TZ:Uno:NEurope/Berlin:NUTC-1} != "" # see unit-tests/Makefile
. error
@@ -41,20 +44,9 @@
.endif
-# As of 2020-08-16, it is not possible to pass the seconds via a
-# variable expression. This is because parsing of the :localtime
-# modifier stops at the '$' and returns to ApplyModifiers.
-#
-# There, a colon would be skipped but not a dollar.
-# Parsing therefore continues at the '$' of the ${:U159...}, looking
-# for an ordinary variable modifier.
-#
-# At this point, the ${:U} is expanded and interpreted as a variable
-# modifier, which results in the error message "Unknown modifier '1'".
-#
-# If ApplyModifier_Localtime were to pass its argument through
-# ParseModifierPart, this would work.
-.if ${%Y:L:localtime=${:U1593536400}} != "mtime=11593536400}"
+# Before var.c 1.1050 from 2023-05-09, it was not possible to pass the
+# seconds via an expression.
+.if ${%Y:L:localtime=${:U1593536400}} != "2020"
. error
.endif
@@ -64,6 +56,7 @@
# 1970. Going back 50 years in the past is not a practical use case for
# make. Therefore, since var.c 1.631, negative time stamps produce a
# parse error.
+# expect+1: Invalid time value "-1"
.if ${:L:localtime=-1} != ""
. error
.else
@@ -73,8 +66,11 @@
# Spaces were allowed before var.c 1.631 from 2020-10-31 21:40:20, not
# because it would make sense but just as a side-effect from using strtoul.
+# expect+1: Invalid time value " 1"
.if ${:L:localtime= 1} != ""
. error
+.else
+. error
.endif
@@ -115,7 +111,9 @@
# ULONG_MAX, which got converted to -1. This resulted in a time stamp of
# the second before 1970.
#
-# Since var.c 1.631, the overflow is detected and produces a parse error.
+# Since var.c 1.631 from 2020-10-31, the overflow is detected and produces a
+# parse error.
+# expect+1: Invalid time value "10000000000000000000000000000000"
.if ${:L:localtime=10000000000000000000000000000000} != ""
. error
.else
@@ -127,11 +125,19 @@
# stopped after the '=', and the remaining string was parsed for more variable
# modifiers. Because of the unknown modifier 'e' from the 'error', the whole
# variable value was discarded and thus not printed.
+# expect+1: Invalid time value "error"
.if ${:L:localtime=error} != ""
. error
.else
. error
.endif
+# Before var.c 1.1050 from 2023-05-09, the timestamp could be directly
+# followed by the next modifier, without a ':' separator. This was the same
+# bug as for the ':L' and ':P' modifiers.
+# expect+1: Invalid time value "100000S,1970,bad,"
+.if ${%Y:L:localtime=100000S,1970,bad,} != "bad"
+. error
+.endif
all:
diff --git a/contrib/bmake/unit-tests/varmod-loop-delete.exp b/contrib/bmake/unit-tests/varmod-loop-delete.exp
index aac86ee39061..daacafc35561 100644
--- a/contrib/bmake/unit-tests/varmod-loop-delete.exp
+++ b/contrib/bmake/unit-tests/varmod-loop-delete.exp
@@ -1,4 +1,6 @@
-make: "varmod-loop-delete.mk" line 19: Cannot delete variable "VAR" while it is used
+make: varmod-loop-delete.mk:20: Cannot delete variable "VAR" while it is used
+ while evaluating "${:U:@VAR@@} rest of the value" with value ""
+ while evaluating variable "VAR" with value "${:U:@VAR@@} rest of the value"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varmod-loop-delete.mk b/contrib/bmake/unit-tests/varmod-loop-delete.mk
index ed145b59ba0f..478a25e91f6e 100644
--- a/contrib/bmake/unit-tests/varmod-loop-delete.mk
+++ b/contrib/bmake/unit-tests/varmod-loop-delete.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-loop-delete.mk,v 1.2 2021/12/05 15:51:33 rillig Exp $
+# $NetBSD: varmod-loop-delete.mk,v 1.7 2024/08/29 20:20:36 rillig Exp $
#
# Tests for the variable modifier ':@', which as a side effect allows to
# delete an arbitrary variable.
@@ -16,6 +16,7 @@ VAR= ${:U:@VAR@@} rest of the value
# In an assignment, the scope is 'Global'. Since the variable 'VAR' is
# defined in the global scope, it deletes itself.
+# expect+1: Cannot delete variable "VAR" while it is used
EVAL:= ${VAR}
.if ${EVAL} != " rest of the value"
. error
diff --git a/contrib/bmake/unit-tests/varmod-loop-varname.exp b/contrib/bmake/unit-tests/varmod-loop-varname.exp
index 4f0379d5ea0a..9320af6c65ab 100644
--- a/contrib/bmake/unit-tests/varmod-loop-varname.exp
+++ b/contrib/bmake/unit-tests/varmod-loop-varname.exp
@@ -1,11 +1,11 @@
-make: "varmod-loop-varname.mk" line 16: In the :@ modifier of "", the variable name "${:Ubar:S,b,v,}" must not contain a dollar
-make: "varmod-loop-varname.mk" line 16: Malformed conditional (${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+")
-make: "varmod-loop-varname.mk" line 85: In the :@ modifier of "1 2 3", the variable name "v$" must not contain a dollar
-make: "varmod-loop-varname.mk" line 85: Malformed conditional (${1 2 3:L:@v$@($v)@} != "(1) (2) (3)")
-make: "varmod-loop-varname.mk" line 90: In the :@ modifier of "1 2 3", the variable name "v$$" must not contain a dollar
-make: "varmod-loop-varname.mk" line 90: Malformed conditional (${1 2 3:L:@v$$@($v)@} != "() () ()")
-make: "varmod-loop-varname.mk" line 95: In the :@ modifier of "1 2 3", the variable name "v$$$" must not contain a dollar
-make: "varmod-loop-varname.mk" line 95: Malformed conditional (${1 2 3:L:@v$$$@($v)@} != "() () ()")
+make: varmod-loop-varname.mk:17: In the :@ modifier, the variable name "${:Ubar:S,b,v,}" must not contain a dollar
+ while evaluating "${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+"" with value "one two three"
+make: varmod-loop-varname.mk:87: In the :@ modifier, the variable name "v$" must not contain a dollar
+ while evaluating variable "1 2 3" with value "1 2 3"
+make: varmod-loop-varname.mk:93: In the :@ modifier, the variable name "v$$" must not contain a dollar
+ while evaluating variable "1 2 3" with value "1 2 3"
+make: varmod-loop-varname.mk:99: In the :@ modifier, the variable name "v$$$" must not contain a dollar
+ while evaluating variable "1 2 3" with value "1 2 3"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varmod-loop-varname.mk b/contrib/bmake/unit-tests/varmod-loop-varname.mk
index 91f8a4876466..703e81941c59 100644
--- a/contrib/bmake/unit-tests/varmod-loop-varname.mk
+++ b/contrib/bmake/unit-tests/varmod-loop-varname.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-loop-varname.mk,v 1.4 2021/12/05 15:01:04 rillig Exp $
+# $NetBSD: varmod-loop-varname.mk,v 1.12 2025/01/11 20:54:46 rillig Exp $
#
# Tests for the first part of the variable modifier ':@var@...@', which
# contains the variable name to use during the loop.
@@ -13,6 +13,7 @@
# dynamically. There was no practical use-case for this.
# Since var.c 1.907 from 2021-04-04, a '$' is no longer allowed in the
# variable name.
+# expect+1: In the :@ modifier, the variable name "${:Ubar:S,b,v,}" must not contain a dollar
.if ${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+"
. error
.else
@@ -82,16 +83,19 @@ RES3= 3
# There's no point in allowing a dollar sign in that position.
# Since var.c 1.907 from 2021-04-04, a '$' is no longer allowed in the
# variable name.
+# expect+1: In the :@ modifier, the variable name "v$" must not contain a dollar
.if ${1 2 3:L:@v$@($v)@} != "(1) (2) (3)"
. error
.else
. error
.endif
+# expect+1: In the :@ modifier, the variable name "v$$" must not contain a dollar
.if ${1 2 3:L:@v$$@($v)@} != "() () ()"
. error
.else
. error
.endif
+# expect+1: In the :@ modifier, the variable name "v$$$" must not contain a dollar
.if ${1 2 3:L:@v$$$@($v)@} != "() () ()"
. error
.else
@@ -104,7 +108,7 @@ RES3= 3
#
# As of 2020-10-18, the :@ modifier is implemented by actually setting a
# variable in the scope of the expression and deleting it again after the
-# loop. This is different from the .for loops, which substitute the variable
+# loop. This is different from the .for loops, which substitute the
# expression with ${:Uvalue}, leading to different unwanted side effects.
#
# To make the behavior more predictable, the :@ modifier should restore the
diff --git a/contrib/bmake/unit-tests/varmod-loop.exp b/contrib/bmake/unit-tests/varmod-loop.exp
index bbe0037673b3..510b151957f8 100644
--- a/contrib/bmake/unit-tests/varmod-loop.exp
+++ b/contrib/bmake/unit-tests/varmod-loop.exp
@@ -1,10 +1,12 @@
-Parsing line 78: USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$
+Parsing varmod-loop.mk:89: USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$
+Parsing varmod-loop.mk:90: .if ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$"
CondParser_Eval: ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$"
Comparing "$$$$ $$$$ $$$$" != "$$$$ $$$$ $$$$"
-Parsing line 83: SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}
+Parsing varmod-loop.mk:94: SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}
+Parsing varmod-loop.mk:116: .if ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$"
CondParser_Eval: ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$"
Comparing "$$ $$$$ $$$$" != "$$ $$$$ $$$$"
-Parsing line 108: .MAKEFLAGS: -d0
+Parsing varmod-loop.mk:119: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
:varname-overwriting-target: :x1y x2y x3y: ::
mod-loop-dollar:1:
@@ -13,4 +15,10 @@ mod-loop-dollar:$3$:
mod-loop-dollar:$${word}$$:
mod-loop-dollar:$$5$$:
mod-loop-dollar:$$${word}$$$:
+: t=$(( ${t:-0} + 1 ))
+: dollar=end
+: backslash=\ end
+: dollar=$ at=@ backslash=\ end
+: dollar=$$ at=@@ backslash=\\ end
+: dollar=$$ at=@@ backslash=\\ end
exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-loop.mk b/contrib/bmake/unit-tests/varmod-loop.mk
index 82046ff95d79..4b4d4d32a058 100644
--- a/contrib/bmake/unit-tests/varmod-loop.mk
+++ b/contrib/bmake/unit-tests/varmod-loop.mk
@@ -1,6 +1,20 @@
-# $NetBSD: varmod-loop.mk,v 1.18 2021/12/05 15:20:13 rillig Exp $
+# $NetBSD: varmod-loop.mk,v 1.26 2024/06/02 15:31:26 rillig Exp $
#
-# Tests for the :@var@...${var}...@ variable modifier.
+# Tests for the expression modifier ':@var@body@', which replaces each word of
+# the expression with the expanded body, which may contain references to the
+# variable 'var'. For example, '${1 2 3:L:@word@<${word}>@}' encloses each
+# word in angle quotes, resulting in '<1> <2> <3>'.
+#
+# The variable name can be chosen freely, except that it must not contain a
+# '$'. For simplicity and readability, variable names should only use the
+# characters 'A-Za-z0-9'.
+#
+# The body may contain subexpressions in the form '${...}' or '$(...)'. These
+# subexpressions differ from everywhere else in makefiles in that the parser
+# only scans '${...}' for balanced '{' and '}', likewise for '$(...)'. Any
+# other '$' is left as-is during parsing. Later, when the body is expanded
+# for each word, each '$$' is interpreted as a single '$', and the remaining
+# '$' are interpreted as expressions, like when evaluating a regular variable.
# 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
@@ -19,7 +33,6 @@ varname-overwriting-target:
@echo :$@: :${:U1 2 3:@\@@x${@}y@}: :$@:
-
# Demonstrate that it is possible to generate dollar signs using the
# :@ modifier.
#
@@ -39,7 +52,7 @@ mod-loop-dollar:
#
# As of 2020-10-18, the :@ modifier is implemented by actually setting a
# variable in the scope of the expression and deleting it again after the
-# loop. This is different from the .for loops, which substitute the variable
+# loop. This is different from the .for loops, which substitute the
# expression with ${:Uvalue}, leading to different unwanted side effects.
#
# To make the behavior more predictable, the :@ modifier should restore the
@@ -69,10 +82,8 @@ mod-loop-dollar:
8_DOLLARS= $$$$$$$$
# This string literal is written with 8 dollars, and this is saved as the
# variable value. But as soon as this value is evaluated, it goes through
-# Var_Subst, which replaces each '$$' with a single '$'. This could be
-# prevented by VARE_EVAL_KEEP_DOLLAR, but that flag is usually removed
-# before expanding subexpressions. See ApplyModifier_Loop and
-# ParseModifierPart for examples.
+# Var_Subst, which replaces each '$$' with a single '$'.
+# See ApplyModifier_Loop and ParseModifierPart for examples.
#
.MAKEFLAGS: -dcp
USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$
@@ -82,11 +93,11 @@ USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$
#
SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}
# The ':=' assignment operator evaluates the variable value using the mode
-# VARE_KEEP_DOLLAR_UNDEF, which means that some dollar signs are preserved,
-# but not all. The dollar signs in the top-level expression and in the
-# indirect ${8_DOLLARS} are preserved.
+# VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED, which means that some dollar signs are
+# preserved, but not all. The dollar signs in the top-level expression and in
+# the indirect ${8_DOLLARS} are preserved.
#
-# The variable modifier :@var@ does not preserve the dollar signs though, no
+# The modifier :@var@ does not preserve the dollar signs though, no
# matter in which context it is evaluated. What happens in detail is:
# First, the modifier part "${8_DOLLARS}" is parsed without expanding it.
# Next, each word of the value is expanded on its own, and at this moment
@@ -98,7 +109,7 @@ SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}
# The variable SUBST_CONTAINING_LOOP therefore gets assigned the raw value
# "$$$$ $$$$$$$$ $$$$$$$$".
#
-# The variable expression in the condition then expands this raw stored value
+# The expression in the condition then expands this raw stored value
# once, resulting in "$$ $$$$ $$$$". The effects from VARE_KEEP_DOLLAR no
# longer take place since they had only been active during the evaluation of
# the variable assignment.
@@ -186,4 +197,48 @@ CMDLINE= global # needed for deleting the environment
. error # 'CMDLINE' is gone now from all scopes
.endif
+
+# In the loop body text of the ':@' modifier, a literal '$' is written as '$$',
+# not '\$'. In the following example, each '$$' turns into a single '$',
+# except for '$i', which is replaced with the then-current value '1' of the
+# iteration variable.
+#
+# See parse-var.mk, keyword 'BRACE_GROUP'.
+all: varmod-loop-literal-dollar
+varmod-loop-literal-dollar: .PHONY
+ : ${:U1:@i@ t=$$(( $${t:-0} + $i ))@}
+
+
+# When parsing the loop body, each '\$', '\@' and '\\' is unescaped to '$',
+# '@' and '\', respectively; all other backslashes are retained.
+#
+# In practice, the '$' is not escaped as '\$', as there is a second round of
+# unescaping '$$' to '$' later when the loop body is expanded after setting the
+# iteration variable.
+#
+# After the iteration variable has been set, the loop body is expanded with
+# this unescaping, regardless of whether .MAKE.SAVE_DOLLARS is set or not:
+# $$ a literal '$'
+# $x, ${var}, $(var) a nested expression
+# any other character itself
+all: escape-modifier
+escape-modifier: .PHONY
+ # In the first round, '\$ ' is unescaped to '$ ', and since the
+ # variable named ' ' is not defined, the expression '$ ' expands to an
+ # empty string.
+ # expect: : dollar=end
+ : ${:U1:@i@ dollar=\$ end@}
+
+ # Like in other modifiers, '\ ' is preserved, since ' ' is not one of
+ # the characters that _must_ be escaped.
+ # expect: : backslash=\ end
+ : ${:U1:@i@ backslash=\ end@}
+
+ # expect: : dollar=$ at=@ backslash=\ end
+ : ${:U1:@i@ dollar=\$\$ at=\@ backslash=\\ end@}
+ # expect: : dollar=$$ at=@@ backslash=\\ end
+ : ${:U1:@i@ dollar=\$\$\$\$ at=\@\@ backslash=\\\\ end@}
+ # expect: : dollar=$$ at=@@ backslash=\\ end
+ : ${:U1:@i@ dollar=$$$$ at=\@\@ backslash=\\\\ end@}
+
all: .PHONY
diff --git a/contrib/bmake/unit-tests/varmod-match-escape.exp b/contrib/bmake/unit-tests/varmod-match-escape.exp
index 25cf6c0719d4..42e470310d4c 100755
--- a/contrib/bmake/unit-tests/varmod-match-escape.exp
+++ b/contrib/bmake/unit-tests/varmod-match-escape.exp
@@ -1,11 +1,11 @@
Global: SPECIALS = \: : \\ * \*
CondParser_Eval: ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}}
-Var_Parse: ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}} (eval-defined)
+Var_Parse: ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}} (eval-defined-loud)
Evaluating modifier ${SPECIALS:M...} on value "\: : \\ * \*"
Pattern for ':M' is "\:"
ModifyWords: split "\: : \\ * \*" into 5 words
Result of ${SPECIALS:M${:U}\:} is ":"
-Var_Parse: ${SPECIALS:M\:${:U}} (eval-defined)
+Var_Parse: ${SPECIALS:M\:${:U}} (eval-defined-loud)
Evaluating modifier ${SPECIALS:M...} on value "\: : \\ * \*"
Pattern for ':M' is ":"
ModifyWords: split "\: : \\ * \*" into 5 words
@@ -13,27 +13,38 @@ Result of ${SPECIALS:M\:${:U}} is ":"
Comparing ":" != ":"
Global: VALUES = : :: :\:
CondParser_Eval: ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:}
-Var_Parse: ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:} (eval-defined)
+Var_Parse: ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:} (eval-defined-loud)
Evaluating modifier ${VALUES:M...} on value ": :: :\:"
-Var_Parse: ${:U:} (eval-defined)
-Evaluating modifier ${:U} on value "" (eval-defined, undefined)
-Result of ${:U} is "" (eval-defined, defined)
+Var_Parse: ${:U:} (eval)
+Evaluating modifier ${:U} on value "" (eval, undefined)
+Result of ${:U} is "" (eval, defined)
Pattern for ':M' is ":"
ModifyWords: split ": :: :\:" into 3 words
Result of ${VALUES:M\:${:U\:}} is ":"
-Var_Parse: ${VALUES:M${:U\:}\:} (eval-defined)
+Var_Parse: ${VALUES:M${:U\:}\:} (eval-defined-loud)
Evaluating modifier ${VALUES:M...} on value ": :: :\:"
-Var_Parse: ${:U\:}\: (eval-defined)
-Evaluating modifier ${:U...} on value "" (eval-defined, undefined)
-Result of ${:U\:} is ":" (eval-defined, defined)
+Var_Parse: ${:U\:}\: (eval)
+Evaluating modifier ${:U...} on value "" (eval, undefined)
+Result of ${:U\:} is ":" (eval, defined)
Pattern for ':M' is ":\:"
ModifyWords: split ": :: :\:" into 3 words
Result of ${VALUES:M${:U\:}\:} is "::"
Comparing ":" != "::"
-make: "varmod-match-escape.mk" line 42: warning: XXX: Oops
+make: varmod-match-escape.mk:43: warning: XXX: Oops
Global: .MAKEFLAGS = -r -k -d cv -d
Global: .MAKEFLAGS = -r -k -d cv -d 0
-make: "varmod-match-escape.mk" line 67: Dollar followed by nothing
+make: varmod-match-escape.mk:63: Unfinished backslash at the end in pattern "\" of modifier ":M"
+ while evaluating "${:U\$:M\$} != """ with value "$"
+make: varmod-match-escape.mk:71: Dollar followed by nothing
+ while evaluating "${:U\$:M\$} != """ with value "$"
+make: varmod-match-escape.mk:71: Unfinished backslash at the end in pattern "\" of modifier ":M"
+ while evaluating "${:U\$:M\$} != """ with value "$"
+make: varmod-match-escape.mk:112: Unfinished character list in pattern "[A-]" of modifier ":M"
+ while evaluating variable "WORDS" with value "A A] A]] B B] B]] ] ]] ]]] a a] a]]"
+ in .for loop from varmod-match-escape.mk:109 with pattern = [A-]
+make: varmod-match-escape.mk:112: Unfinished character list in pattern "[^A-]" of modifier ":M"
+ while evaluating variable "WORDS" with value "A A] A]] B B] B]] ] ]] ]]] a a] a]]"
+ in .for loop from varmod-match-escape.mk:109 with pattern = [^A-]
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varmod-match-escape.mk b/contrib/bmake/unit-tests/varmod-match-escape.mk
index 5ac69f964a68..5c492d9d1f72 100755
--- a/contrib/bmake/unit-tests/varmod-match-escape.mk
+++ b/contrib/bmake/unit-tests/varmod-match-escape.mk
@@ -1,8 +1,8 @@
-# $NetBSD: varmod-match-escape.mk,v 1.7 2021/04/03 11:08:40 rillig Exp $
+# $NetBSD: varmod-match-escape.mk,v 1.20 2025/06/28 22:39:29 rillig Exp $
#
# As of 2020-08-01, the :M and :N modifiers interpret backslashes differently,
-# depending on whether there was a variable expression somewhere before the
-# first backslash or not. See ApplyModifier_Match, "copy = true".
+# depending on whether there was an expression somewhere before the
+# first backslash or not. See ParseModifier_Match, "copy = true".
#
# Apart from the different and possibly confusing debug output, there is no
# difference in behavior. When parsing the modifier text, only \{, \} and \:
@@ -18,27 +18,28 @@ SPECIALS= \: : \\ * \*
.endif
# And now both cases combined: A single modifier with both an escaped ':'
-# as well as a variable expression that expands to a ':'.
+# as well as an expression that expands to a ':'.
#
-# XXX: As of 2020-11-01, when an escaped ':' occurs before the variable
+# XXX: As of 2020-11-01, when an escaped ':' occurs before the
# expression, the whole modifier text is subject to unescaping '\:' to ':',
-# before the variable expression is expanded. This means that the '\:' in
-# the variable expression is expanded as well, turning ${:U\:} into a simple
+# before the expression is expanded. This means that the '\:' in
+# the expression is expanded as well, turning ${:U\:} into a simple
# ${:U:}, which silently expands to an empty string, instead of generating
# an error message.
#
# XXX: As of 2020-11-01, the modifier on the right-hand side of the
-# comparison is parsed differently though. First, the variable expression
+# comparison is parsed differently though. First, the expression
# is parsed, resulting in ':' and needSubst=true. After that, the escaped
# ':' is seen, and this time, copy=true is not executed but stays copy=false.
# Therefore the escaped ':' is kept as-is, and the final pattern becomes
# ':\:'.
#
-# If ApplyModifier_Match had used the same parsing algorithm as Var_Subst,
+# If ParseModifier_Match had used the same parsing algorithm as Var_Subst,
# both patterns would end up as '::'.
#
VALUES= : :: :\:
.if ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:}
+# expect+1: warning: XXX: Oops
. warning XXX: Oops
.endif
@@ -52,18 +53,21 @@ VALUES= : :: :\:
.endif
# XXX: As of 2020-11-01, unlike all other variable modifiers, '\$' is not
-# parsed as an escaped '$'. Instead, ApplyModifier_Match first scans for
+# parsed as an escaped '$'. Instead, ParseModifier_Match first scans for
# the ':' at the end of the modifier, which results in the pattern '\$'.
# No unescaping takes place since the pattern neither contained '\:' nor
# '\{' nor '\}'. But the text is expanded, and a lonely '$' at the end
# is silently discarded. The resulting expanded pattern is thus '\', that
# is a single backslash.
+# expect+1: Unfinished backslash at the end in pattern "\" of modifier ":M"
.if ${:U\$:M\$} != ""
. error
.endif
# In lint mode, the case of a lonely '$' is covered with an error message.
.MAKEFLAGS: -dL
+# expect+2: Dollar followed by nothing
+# expect+1: Unfinished backslash at the end in pattern "\" of modifier ":M"
.if ${:U\$:M\$} != ""
. error
.endif
@@ -75,12 +79,41 @@ VALUES= : :: :\:
#
# TODO: Str_Match("a-z]", "[a-z]")
# TODO: Str_Match("012", "[0-]]")
-# TODO: Str_Match("0]", "[0-]]")
-# TODO: Str_Match("1]", "[0-]]")
# TODO: Str_Match("[", "[[]")
# TODO: Str_Match("]", "[]")
# TODO: Str_Match("]", "[[-]]")
+# Demonstrate an inconsistency between positive and negative character lists
+# when the range ends with the character ']'.
+#
+# 'A' begins the range, 'B' is in the middle of the range, ']' ends the range,
+# 'a' is outside the range.
+WORDS= A A] A]] B B] B]] ] ]] ]]] a a] a]]
+# The ']' is part of the character range and at the same time ends the
+# character list.
+EXP.[A-]= A B ]
+# The first ']' is part of the character range and at the same time ends the
+# character list.
+EXP.[A-]]= A] B] ]]
+# The first ']' is part of the character range and at the same time ends the
+# character list.
+EXP.[A-]]]= A]] B]] ]]]
+# For negative character lists, the ']' ends the character range but does not
+# end the character list.
+# XXX: This is unnecessarily inconsistent but irrelevant in practice as there
+# is no practical need for a character range that ends at ']'.
+EXP.[^A-]= a
+EXP.[^A-]]= a
+EXP.[^A-]]]= a]
+
+.for pattern in [A-] [A-]] [A-]]] [^A-] [^A-]] [^A-]]]
+# expect+2: Unfinished character list in pattern "[A-]" of modifier ":M"
+# expect+1: Unfinished character list in pattern "[^A-]" of modifier ":M"
+. if ${WORDS:M${pattern}} != ${EXP.${pattern}}
+. warning ${pattern}: ${WORDS:M${pattern}} != ${EXP.${pattern}}
+. endif
+.endfor
+
# In brackets, the backslash is just an ordinary character.
# Outside brackets, it is an escape character for a few special characters.
# TODO: Str_Match("\\", "[\\-]]")
diff --git a/contrib/bmake/unit-tests/varmod-match.exp b/contrib/bmake/unit-tests/varmod-match.exp
index e4ad3ed113f3..7bccc4283e32 100644
--- a/contrib/bmake/unit-tests/varmod-match.exp
+++ b/contrib/bmake/unit-tests/varmod-match.exp
@@ -1,16 +1,23 @@
-CondParser_Eval: ${NUMBERS:M[A-Z]*} != "One Two Three Four"
-Comparing "One Two Three Four" != "One Two Three Four"
-CondParser_Eval: ${NUMBERS:M[^A-Z]*} != "five six seven"
-Comparing "five six seven" != "five six seven"
-CondParser_Eval: ${NUMBERS:M[^s]*[ex]} != "One Three five"
-Comparing "One Three five" != "One Three five"
-CondParser_Eval: ${:U****************:M****************b}
-CondParser_Eval: ${:Ua \$ sign:M*$$*} != "\$"
-Comparing "$" != "$"
-CondParser_Eval: ${:Ua \$ sign any-asterisk:M*\$*} != "any-asterisk"
-Comparing "any-asterisk" != "any-asterisk"
-make: "varmod-match.mk" line 146: Unknown modifier "]"
-make: "varmod-match.mk" line 146: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":")
+make: varmod-match.mk:293: Unfinished character list in pattern "a[" of modifier ":M"
+ while evaluating variable "WORDS" with value "a a["
+make: varmod-match.mk:301: Unfinished character list in pattern "a[^" of modifier ":M"
+ while evaluating variable "WORDS" with value "a a[ aX"
+make: varmod-match.mk:309: Unfinished character list in pattern "[-x1-3" of modifier ":M"
+ while evaluating variable "WORDS" with value "- + x xx 0 1 2 3 4 [x1-3"
+make: varmod-match.mk:317: Unfinished character list in pattern "*[-x1-3" of modifier ":M"
+ while evaluating variable "WORDS" with value "- + x xx 0 1 2 3 4 00 01 10 11 000 001 010 011 100 101 110 111 [x1-3"
+make: varmod-match.mk:326: Unfinished character list in pattern "[^-x1-3" of modifier ":M"
+ while evaluating variable "WORDS" with value "- + x xx 0 1 2 3 4 [x1-3"
+make: varmod-match.mk:340: Unfinished character list in pattern "?[\" of modifier ":M"
+ while evaluating variable "WORDS" with value "\\ \a x\"
+make: varmod-match.mk:348: Unfinished character range in pattern "[x-" of modifier ":M"
+ while evaluating variable "WORDS" with value "[x- x x- y"
+make: varmod-match.mk:360: Unfinished character range in pattern "[^x-" of modifier ":M"
+ while evaluating variable "WORDS" with value "[x- x x- y yyyyy"
+make: varmod-match.mk:367: Unfinished character list in pattern "[" of modifier ":M"
+ while evaluating variable " : :: " with value " : :: "
+make: varmod-match.mk:367: Unknown modifier ":]"
+ while evaluating variable " : :: " with value ""
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varmod-match.mk b/contrib/bmake/unit-tests/varmod-match.mk
index adea273e530a..5894196c9cd5 100644
--- a/contrib/bmake/unit-tests/varmod-match.mk
+++ b/contrib/bmake/unit-tests/varmod-match.mk
@@ -1,87 +1,99 @@
-# $NetBSD: varmod-match.mk,v 1.8 2022/03/27 18:39:01 rillig Exp $
+# $NetBSD: varmod-match.mk,v 1.32 2025/06/29 09:40:13 rillig Exp $
#
-# Tests for the :M variable modifier, which filters words that match the
+# Tests for the ':M' modifier, which keeps only those words that match the
# given pattern.
#
-# See ApplyModifier_Match and ModifyWord_Match for the implementation.
+# Table of contents
+#
+# 1. Pattern characters '*', '?' and '\'
+# 2. Character lists and character ranges
+# 3. Parsing and escaping
+# 4. Interaction with other modifiers
+# 5. Performance
+# 6. Error handling
+# 7. Historical bugs
+#
+# See also:
+# char-005c-reverse-solidus.mk
+# ApplyModifier_Match
+# ParseModifier_Match
+# ModifyWord_Match
+# Str_Match
-.MAKEFLAGS: -dc
-NUMBERS= One Two Three Four five six seven
+# 1. Pattern characters '*', '?' and '\'
+#
+# * matches 0 or more characters
+# ? matches 1 character
+# \x matches the character 'x'
-# Only keep words that start with an uppercase letter.
-.if ${NUMBERS:M[A-Z]*} != "One Two Three Four"
+# The pattern is anchored both at the beginning and at the end of the word.
+# Since the pattern 'e' does not contain any pattern matching characters, it
+# matches exactly the word 'e', twice.
+.if ${a c e aa cc ee e f g:L:Me} != "e e"
. error
.endif
-# Only keep words that start with a character other than an uppercase letter.
-.if ${NUMBERS:M[^A-Z]*} != "five six seven"
+# The pattern character '?' matches exactly 1 character, the pattern character
+# '*' matches 0 or more characters. The whole pattern matches all words that
+# start with 's' and have 3 or more characters.
+.if ${One Two Three Four five six seven so s:L:Ms??*} != "six seven"
. error
.endif
-# Only keep words that don't start with s and at the same time end with
-# either of [ex].
-#
-# This test case ensures that the negation from the first character class
-# does not propagate to the second character class.
-.if ${NUMBERS:M[^s]*[ex]} != "One Three five"
+# A pattern without placeholders only matches itself.
+.if ${a aa aaa b ba baa bab:L:Ma} != "a"
. error
.endif
-# Before 2020-06-13, this expression took quite a long time in Str_Match,
-# calling itself 601080390 times for 16 asterisks.
-.if ${:U****************:M****************b}
+# A pattern that does not start with '*' is anchored at the beginning.
+.if ${a aa aaa b ba baa bab:L:Ma*} != "a aa aaa"
+. error
.endif
-# To match a dollar sign in a word, double it.
-#
-# This is different from the :S and :C variable modifiers, where a '$'
-# has to be escaped as '\$'.
-.if ${:Ua \$ sign:M*$$*} != "\$"
+# A pattern that does not end with '*' is anchored at the end.
+.if ${a aa aaa b ba baa bab:L:M*a} != "a aa aaa ba baa"
. error
.endif
-# In the :M modifier, '\$' does not escape a dollar. Instead it is
-# interpreted as a backslash followed by whatever expression the
-# '$' starts.
-#
-# This differs from the :S, :C and several other variable modifiers.
-${:U*}= asterisk
-.if ${:Ua \$ sign any-asterisk:M*\$*} != "any-asterisk"
+# Test the fast code path for '*' followed by a regular character.
+.if ${:U file.c file.*c file.h file\.c :M*.c} != "file.c file\\.c"
+. error
+.endif
+# Ensure that the fast code path correctly handles the backslash.
+.if ${:U file.c file.*c file.h file\.c :M*\.c} != "file.c file\\.c"
+. error
+.endif
+# Ensure that the fast code path correctly handles '\*'.
+.if ${:U file.c file.*c file.h file\.c :M*\*c} != "file.*c"
+. error
+.endif
+# Ensure that the partial match '.c' doesn't confuse the fast code path.
+.if ${:U file.c.cc file.cc.cc file.cc.c :M*.cc} != "file.c.cc file.cc.cc"
+. error
+.endif
+# Ensure that the substring '.cc' doesn't confuse the fast code path for '.c'.
+.if ${:U file.c.cc file.cc.cc file.cc.c :M*.c} != "file.cc.c"
. error
.endif
-# TODO: ${VAR:M(((}}}}
-# TODO: ${VAR:M{{{)))}
-# TODO: ${VAR:M${UNBALANCED}}
-# TODO: ${VAR:M${:U(((\}\}\}}}
-.MAKEFLAGS: -d0
-
-# Special characters:
-# * matches 0 or more arbitrary characters
-# ? matches a single arbitrary character
-# \ starts an escape sequence, only outside ranges
-# [ starts a set for matching a single character
-# ] ends a set for matching a single character
-# - in a set, forms a range of characters
-# ^ as the first character in a set, negates the set
-# ( during parsing of the pattern, starts a nesting level
-# ) during parsing of the pattern, ends a nesting level
-# { during parsing of the pattern, starts a nesting level
-# } during parsing of the pattern, ends a nesting level
-# : during parsing of the pattern, finishes the pattern
-# $ during parsing of the pattern, starts a nested expression
-# # in a line except a shell command, starts a comment
-#
-# Pattern parts:
-# * matches 0 or more arbitrary characters
-# ? matches exactly 1 arbitrary character
-# \x matches exactly the character 'x'
-# [...] matches exactly 1 character from the set
-# [^...] matches exactly 1 character outside the set
-# [a-z] matches exactly 1 character from the range 'a' to 'z'
+# 2. Character lists and character ranges
#
+# [...] matches 1 character from the listed characters
+# [^...] matches 1 character from the unlisted characters
+# [a-z] matches 1 character from the range 'a' to 'z'
+# [z-a] matches 1 character from the range 'a' to 'z'
+
+# Only keep words that start with an uppercase letter.
+.if ${One Two Three Four five six seven:L:M[A-Z]*} != "One Two Three Four"
+. error
+.endif
+
+# Only keep words that start with a character other than an uppercase letter.
+.if ${One Two Three Four five six seven:L:M[^A-Z]*} != "five six seven"
+. error
+.endif
# [] matches never
.if ${ ab a[]b a[b a b :L:M[]} != ""
@@ -125,6 +137,82 @@ ${:U*}= asterisk
. error
.endif
+# [\] matches a single backslash; no escaping takes place in
+# character ranges
+# Without the 'b' in the below words, the backslash would end a word and thus
+# influence how the string is split into words.
+WORDS= a\b a[\]b ab a\\b
+.if ${WORDS:Ma[\]b} != "a\\b"
+. error
+.endif
+
+# [[-]] May look like it would match a single '[', '\' or ']', but
+# the inner ']' has two roles: it is the upper bound of the
+# character range as well as the closing character of the
+# character list. The outer ']' is just a regular character.
+WORDS= [ ] [] \] ]]
+.if ${WORDS:M[[-]]} != "[] \\] ]]"
+. error
+.endif
+
+# [b[-]a]
+# Same as for '[[-]]': the character list stops at the first
+# ']', and the 'a]' is treated as a literal string.
+WORDS= [a \a ]a []a \]a ]]a [a] \a] ]a] ba]
+.if ${WORDS:M[b[-]a]} != "[a] \\a] ]a] ba]"
+. error
+.endif
+
+# [-] Matches a single '-' since the '-' only becomes part of a
+# character range if it is preceded and followed by another
+# character.
+WORDS= - -]
+.if ${WORDS:M[-]} != "-"
+. error
+.endif
+
+# Only keep words that don't start with s and at the same time end with
+# either of [ex].
+#
+# This test case ensures that the negation from the first character list
+# '[^s]' does not propagate to the second character list '[ex]'.
+.if ${One Two Three Four five six seven:L:M[^s]*[ex]} != "One Three five"
+. error
+.endif
+
+
+# 3. Parsing and escaping
+#
+# * matches 0 or more characters
+# ? matches 1 character
+# \ outside a character list, escapes the following character
+# [ starts a character list for matching 1 character
+# ] ends a character list for matching 1 character
+# - in a character list, forms a character range
+# ^ at the beginning of a character list, negates the list
+# ( while parsing the pattern, starts a nesting level
+# ) while parsing the pattern, ends a nesting level
+# { while parsing the pattern, starts a nesting level
+# } while parsing the pattern, ends a nesting level
+# : while parsing the pattern, terminates the pattern
+# $ while parsing the pattern, starts a nested expression
+# # in a line except a shell command, starts a comment
+
+# The pattern can come from an expression. For single-letter
+# variables, either the short form or the long form can be used, just as
+# everywhere else.
+PRIMES= 2 3 5 7 11
+n= 2
+.if ${PRIMES:M$n} != "2"
+. error
+.endif
+.if ${PRIMES:M${n}} != "2"
+. error
+.endif
+.if ${PRIMES:M${:U2}} != "2"
+. error
+.endif
+
# : terminates the pattern
.if ${ A * :L:M:} != ""
. error
@@ -140,38 +228,31 @@ ${:U*}= asterisk
. error
.endif
-# [:] matches never since the ':' starts the next modifier
-# expect+2: Unknown modifier "]"
-# expect+1: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":")
-.if ${ ${:U\:} ${:U\:\:} :L:M[:]} != ":"
-. error
-.else
+# To match a dollar sign in a word, double it.
+#
+# This is different from the :S and :C modifiers, where a '$' has to be
+# escaped as '\$'.
+.if ${:Ua \$ sign:M*$$*} != "\$"
. error
.endif
-# [\] matches exactly a backslash; no escaping takes place in
-# character ranges
-# Without the 'a' in the below expressions, the backslash would end a word and
-# thus influence how the string is split into words.
-.if ${ ${:U\\a} ${:U\\\\a} :L:M[\]a} != "\\a"
+# In the :M modifier, '\$' does not escape a dollar. Instead it is
+# interpreted as a backslash followed by whatever expression the
+# '$' starts.
+#
+# This differs from the :S, :C and several other modifiers.
+${:U*}= asterisk
+.if ${:Ua \$ sign any-asterisk:M*\$*} != "any-asterisk"
. error
.endif
-#.MAKEFLAGS: -dcv
-#
-# Incomplete patterns:
-# [ matches TODO
-# [x matches TODO
-# [^ matches TODO
-# [- matches TODO
-# [xy matches TODO
-# [^x matches TODO
-# [\ matches TODO
-#
-# [x- matches exactly 'x', doesn't match 'x-'
-# [^x- matches TODO
-# \ matches never
+# TODO: ${VAR:M(((}}}}
+# TODO: ${VAR:M{{{)))}
+# TODO: ${VAR:M${UNBALANCED}}
+# TODO: ${VAR:M${:U(((\}\}\}}}
+
+# 4. Interaction with other modifiers
# The modifier ':tW' prevents splitting at whitespace. Even leading and
# trailing whitespace is preserved.
@@ -179,8 +260,131 @@ ${:U*}= asterisk
. error
.endif
-# Without the modifier ':tW', the string is split into words. All whitespace
-# around and between the words is normalized to a single space.
+# Without the modifier ':tW', the string is split into words. Whitespace
+# around the words is discarded, and whitespace between the words is
+# normalized to a single space.
.if ${ plain string :L:M*} != "plain string"
. error
.endif
+
+
+# 5. Performance
+
+# Before 2020-06-13, this expression called Str_Match 601,080,390 times.
+# Since 2020-06-13, this expression calls Str_Match 1 time.
+.if ${:U****************:M****************b}
+.endif
+
+# Before 2023-06-22, this expression called Str_Match 2,621,112 times.
+# Adding another '*?' to the pattern called Str_Match 20,630,572 times.
+# Adding another '*?' to the pattern called Str_Match 136,405,672 times.
+# Adding another '*?' to the pattern called Str_Match 773,168,722 times.
+# Adding another '*?' to the pattern called Str_Match 3,815,481,072 times.
+# Since 2023-06-22, Str_Match no longer backtracks.
+.if ${:U..................................................b:M*?*?*?*?*?a}
+.endif
+
+
+# 6. Error handling
+
+# [ Incomplete empty character list, never matches.
+WORDS= a a[
+# expect+1: Unfinished character list in pattern "a[" of modifier ":M"
+.if ${WORDS:Ma[} != ""
+. error
+.endif
+
+# [^ Incomplete negated empty character list, matches any single
+# character.
+WORDS= a a[ aX
+# expect+1: Unfinished character list in pattern "a[^" of modifier ":M"
+.if ${WORDS:Ma[^} != "a[ aX"
+. error
+.endif
+
+# [-x1-3 Incomplete character list, matches those elements that can be
+# parsed without lookahead.
+WORDS= - + x xx 0 1 2 3 4 [x1-3
+# expect+1: Unfinished character list in pattern "[-x1-3" of modifier ":M"
+.if ${WORDS:M[-x1-3} != "- x 1 2 3"
+. error
+.endif
+
+# *[-x1-3 Incomplete character list after a wildcard, matches those
+# words that end with one of the characters from the list.
+WORDS= - + x xx 0 1 2 3 4 00 01 10 11 000 001 010 011 100 101 110 111 [x1-3
+# expect+1: Unfinished character list in pattern "*[-x1-3" of modifier ":M"
+.if ${WORDS:M*[-x1-3} != "- x xx 1 2 3 01 11 001 011 101 111 [x1-3"
+. warning ${WORDS:M*[-x1-3}
+.endif
+
+# [^-x1-3
+# Incomplete negated character list, matches any character
+# except those elements that can be parsed without lookahead.
+WORDS= - + x xx 0 1 2 3 4 [x1-3
+# expect+1: Unfinished character list in pattern "[^-x1-3" of modifier ":M"
+.if ${WORDS:M[^-x1-3} != "+ 0 4"
+. error
+.endif
+
+# [\ Incomplete character list containing a single '\'.
+#
+# A word can only end with a backslash if the preceding
+# character is a backslash as well; in all other cases the final
+# backslash would escape the following space, making the space
+# part of the word. Only the very last word of a string can be
+# '\', as there is no following space that could be escaped.
+WORDS= \\ \a ${:Ux\\}
+PATTERN= ${:U?[\\}
+# expect+1: Unfinished character list in pattern "?[\" of modifier ":M"
+.if ${WORDS:M${PATTERN}} != "\\\\ x\\"
+. error
+.endif
+
+# [x- Incomplete character list containing an incomplete character
+# range, matches only the 'x'.
+WORDS= [x- x x- y
+# expect+1: Unfinished character range in pattern "[x-" of modifier ":M"
+.if ${WORDS:M[x-} != "x"
+. error
+.endif
+
+# [^x- Incomplete negated character list containing an incomplete
+# character range; matches each word that does not have an 'x'
+# at the position of the character list.
+#
+# XXX: Even matches strings that are longer than a single
+# character.
+WORDS= [x- x x- y yyyyy
+# expect+1: Unfinished character range in pattern "[^x-" of modifier ":M"
+.if ${WORDS:M[^x-} != "[x- y yyyyy"
+. error
+.endif
+
+# [:] matches never since the ':' starts the next modifier
+# expect+2: Unfinished character list in pattern "[" of modifier ":M"
+# expect+1: Unknown modifier ":]"
+.if ${ ${:U\:} ${:U\:\:} :L:M[:]} != ":"
+. error
+.else
+. error
+.endif
+
+
+# 7. Historical bugs
+
+# Before var.c 1.1031 from 2022-08-24, the following expressions caused an
+# out-of-bounds read beyond the indirect ':M' modifiers.
+#
+# The argument to the inner ':U' is unescaped to 'M\'.
+# This 'M\' becomes an indirect modifier ':M' with the pattern '\'.
+# The pattern '\' never matches.
+.if ${:U:${:UM\\}}
+. error
+.endif
+# The argument to the inner ':U' is unescaped to 'M\:\'.
+# This 'M\:\' becomes an indirect modifier ':M' with the pattern ':\'.
+# The pattern ':\' never matches.
+.if ${:U:${:UM\\\:\\}}
+. error
+.endif
diff --git a/contrib/bmake/unit-tests/varmod-mtime.exp b/contrib/bmake/unit-tests/varmod-mtime.exp
new file mode 100644
index 000000000000..53b86b99b867
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-mtime.exp
@@ -0,0 +1,15 @@
+make: varmod-mtime.mk:46: Invalid argument "123x" for modifier ":mtime"
+ while evaluating variable "no/such/file" with value "no/such/file"
+make: varmod-mtime.mk:68: Cannot determine mtime for "no/such/file1": <ENOENT>
+ while evaluating variable "no/such/file1 no/such/file2" with value "no/such/file1 no/such/file2"
+make: varmod-mtime.mk:68: Cannot determine mtime for "no/such/file2": <ENOENT>
+ while evaluating variable "no/such/file1 no/such/file2" with value "no/such/file1 no/such/file2"
+make: varmod-mtime.mk:78: Invalid argument "errorhandler-no" for modifier ":mtime"
+ while evaluating variable "MAKEFILE" with value "varmod-mtime.mk"
+make: varmod-mtime.mk:86: Invalid argument "warn" for modifier ":mtime"
+ while evaluating variable "MAKEFILE" with value "varmod-mtime.mk"
+make: varmod-mtime.mk:110: Unknown modifier ":mtim"
+ while evaluating variable "anything" with value "anything"
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/varmod-mtime.mk b/contrib/bmake/unit-tests/varmod-mtime.mk
new file mode 100644
index 000000000000..aed7024efd6b
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-mtime.mk
@@ -0,0 +1,120 @@
+# $NetBSD: varmod-mtime.mk,v 1.17 2025/06/28 22:39:29 rillig Exp $
+#
+# Tests for the ':mtime' variable modifier, which maps each word of the
+# expression to that file's modification time.
+
+# Note: strftime() uses mktime() for %s and mktime() assumes localtime
+# so this should match time()
+start:= ${%s:L:localtime} # see varmod-gmtime.mk, keyword '%s'
+
+
+# Ensure that this makefile exists and has a modification time. If the file
+# didn't exist, the ':mtime' modifier would return the current time.
+.if ${MAKEFILE:mtime} >= ${start}
+. error
+.endif
+
+
+# For a file that doesn't exist, the ':mtime' modifier returns the current
+# time, without an error or warning message. The returned timestamp differs
+# from the 'now' that is used when updating the timestamps in archives or for
+# touching files using the '-t' option, which is taken once when make is
+# started.
+not_found_mtime:= ${no/such/file:L:mtime}
+.if ${not_found_mtime} < ${start}
+. error
+.endif
+
+
+# The ':mtime' modifier accepts a timestamp in seconds as an optional
+# argument. This timestamp is used as a fallback in case the file's time
+# cannot be determined, without any error or warning message.
+.if ${no/such/file:L:mtime=0} != "0"
+. error
+.endif
+
+
+# The fallback timestamp must start with a digit, and it is interpreted as a
+# decimal integer.
+.if ${no/such/file:L:mtime=00042} != "42"
+. error
+.endif
+
+
+# The fallback timestamp must only be an integer, without trailing characters.
+# expect+1: Invalid argument "123x" for modifier ":mtime"
+.if ${no/such/file:L:mtime=123x}
+. error
+.else
+. error
+.endif
+
+
+# The timestamp of a newly created file must be at least as great as the
+# timestamp when parsing of this makefile started.
+COOKIE= ${TMPDIR:U/tmp}/varmod-mtime.cookie
+_!= touch ${COOKIE}
+.if ${COOKIE:mtime=0} < ${start}
+. error ${COOKIE:mtime=0} < ${start}
+.endif
+_!= rm -f ${COOKIE}
+
+
+# If the optional argument of the ':mtime' modifier is the word 'error', the
+# modifier fails with an error message, once for each affected file.
+#
+# expect+2: Cannot determine mtime for "no/such/file1": <ENOENT>
+# expect+1: Cannot determine mtime for "no/such/file2": <ENOENT>
+.if ${no/such/file1 no/such/file2:L:mtime=error}
+. error
+.else
+. error
+.endif
+
+
+# Only the word 'error' is a special argument to the ':mtime' modifier, all
+# other words result in a parse error.
+# expect+1: Invalid argument "errorhandler-no" for modifier ":mtime"
+.if ${MAKEFILE:mtime=errorhandler-no} > 0
+.else
+. error
+.endif
+
+
+# Only the word 'error' can be used as a fallback argument to the modifier.
+# expect+1: Invalid argument "warn" for modifier ":mtime"
+.if ${MAKEFILE:mtime=warn} > 0
+. error
+.else
+. error
+.endif
+
+
+# Ensure that the fallback for a missing modification time is indeed the
+# current time, and not any later time.
+end:= ${%s:L:gmtime}
+.if ${not_found_mtime} > ${end}
+. error
+.endif
+
+
+# If the expression is irrelevant, the ':mtime' modifier is only parsed, it
+# does not perform any filesystem operations.
+.if 0 && ${no/such/file:L:mtime=error}
+. error
+.endif
+
+
+# If there is a typo in the modifier name, it does not match.
+# expect+1: Unknown modifier ":mtim"
+.if ${anything:L:mtim}
+. error
+.else
+. error
+.endif
+
+
+# An empty word list results in an empty mtime list.
+.if ${:U:mtime} != ""
+. error
+.endif
diff --git a/contrib/bmake/unit-tests/varmod-no-match.mk b/contrib/bmake/unit-tests/varmod-no-match.mk
index 2acb27e2e727..c03b4bf94e70 100644
--- a/contrib/bmake/unit-tests/varmod-no-match.mk
+++ b/contrib/bmake/unit-tests/varmod-no-match.mk
@@ -1,9 +1,97 @@
-# $NetBSD: varmod-no-match.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varmod-no-match.mk,v 1.3 2023/02/26 06:08:06 rillig Exp $
#
-# Tests for the :N variable modifier, which filters words that do not match
-# the given pattern.
+# Tests for the expression modifier ':N', which filters words that do not
+# match the given pattern.
+
+
+# Keep all words except for 'two'.
+.if ${:U one two three :Ntwo} != "one three"
+. error
+.endif
+
+# Keep all words except those starting with 't'.
+# See varmod-match.mk for the details of pattern matching.
+.if ${:U one two three four six :Nt*} != "one four six"
+. error
+.endif
+
+
+# Idiom: normalize whitespace
+#
+# The modifier ':N' can be used with an empty pattern. As that pattern never
+# matches a word, the only effect is that the string is split into words and
+# then joined again, thereby normalizing whitespace around and between the
+# words. And even though the 'N' in ':N' might serve as a mnemonic for
+# "normalize whitespace", this idiom is not used in practice, resorting to the
+# much more common ':M*' to "select all words" instead.
+.if ${:U :N} != ""
+. error
+.endif
+.if ${:U one two three :N} != "one two three"
+. error
+.endif
+.if ${:U one two three :M*} != "one two three"
+. error
+.endif
+
+
+# Idiom: single-word expression equals any of several words or patterns
+#
+# If an expression is guaranteed to consist of a single word, the modifier
+# ':N' can be chained to compare the expression to several words or even
+# patterns in a sequence. If one of the patterns matches, the final
+# expression will be the empty string.
+#
+.if ${:U word :None:Ntwo:Nthree} != ""
+# good
+.else
+. error
+.endif
+.if ${:U two :None:Ntwo:Nthree} != ""
+. error
+.else
+# good
+.endif
+#
+# The modifier ':N' is seldom used in general since positive matches with ':M'
+# are easier to grasp. Chaining the ':N' modifier is even more difficult to
+# grasp due to the many negations involved.
+#
+# The final '!= ""' adds to the confusion because at first glance, the
+# condition may look like '${VAR} != ""', which for a single-word variable is
+# always true.
+#
+# The '!= ""' can be omitted if the expression cannot have the numeric value
+# 0, which is common in practice. In that form, each ':N' can be pronounced
+# as 'neither' or 'nor', which makes the expression sound more natural.
+#
+.if ${:U word :None:Ntwo:Nthree}
+# good
+.else
+. error
+.endif
+.if ${:U two :None:Ntwo:Nthree}
+. error
+.else
+# good
+.endif
+#
+# Replacing the '${...} != ""' with '!empty(...)' doesn't improve the
+# situation as the '!' adds another level of negations, and the word 'empty'
+# is a negation on its own, thereby creating a triple negation. Furthermore,
+# due to the '!empty', the expression to be evaluated no longer starts with
+# '$' and is thus more difficult to spot quickly.
+#
+.if !empty(:U word :None:Ntwo:Nthree)
+# good
+.else
+. error
+.endif
+.if !empty(:U two :None:Ntwo:Nthree)
+. error
+.else
+# good
+.endif
-# TODO: Implementation
all:
- @:;
diff --git a/contrib/bmake/unit-tests/varmod-order-numeric.mk b/contrib/bmake/unit-tests/varmod-order-numeric.mk
index 542894c53942..62212bd265ad 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.7 2022/02/09 21:09:24 rillig Exp $
+# $NetBSD: varmod-order-numeric.mk,v 1.8 2022/09/27 19:18:45 rillig Exp $
#
# Tests for the variable modifiers ':On', which returns the words, sorted in
# ascending numeric order, and for ':Orn' and ':Onr', which additionally
@@ -50,4 +50,10 @@ MIXED_BASE= 0 010 0x7 9
. error ${MIXED_BASE:On}
.endif
+# The measurement units for suffixes are k, M, G, but not T.
+# The string '3T' evaluates to 3, the string 'x' evaluates as '0'.
+.if ${4 3T 2M x:L:On} != "x 3T 4 2M"
+. error
+.endif
+
all:
diff --git a/contrib/bmake/unit-tests/varmod-order-shuffle.mk b/contrib/bmake/unit-tests/varmod-order-shuffle.mk
index 16121d7e498f..e9898600355a 100644
--- a/contrib/bmake/unit-tests/varmod-order-shuffle.mk
+++ b/contrib/bmake/unit-tests/varmod-order-shuffle.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-order-shuffle.mk,v 1.7 2021/08/03 04:46:49 rillig Exp $
+# $NetBSD: varmod-order-shuffle.mk,v 1.8 2023/02/26 06:08:06 rillig Exp $
#
# Tests for the :Ox variable modifier, which returns the words of the
# variable, shuffled.
@@ -6,8 +6,9 @@
# The variable modifier :Ox is available since 2005-06-01.
#
# As of 2020-08-16, make uses random(3) seeded by the current time in seconds.
-# This makes the random numbers completely predictable since there is no other
-# part of make that uses random numbers.
+# This makes the random numbers completely predictable since the only other
+# part of make that uses random numbers is the 'randomize-targets' mode, which
+# is off by default.
#
# Tags: probabilistic
diff --git a/contrib/bmake/unit-tests/varmod-order.exp b/contrib/bmake/unit-tests/varmod-order.exp
index 46dc45e9f6d6..fd18f0e11ee1 100644
--- a/contrib/bmake/unit-tests/varmod-order.exp
+++ b/contrib/bmake/unit-tests/varmod-order.exp
@@ -1,24 +1,27 @@
-make: Bad modifier ":OX" for variable "WORDS"
-make: "varmod-order.mk" line 14: Undefined variable "${WORDS:OX"
-make: Bad modifier ":OxXX" for variable "WORDS"
-make: "varmod-order.mk" line 17: Undefined variable "${WORDS:Ox"
-make: Unclosed variable expression, expecting '}' for modifier "O" of variable "WORDS" with value "eight five four nine one seven six ten three two"
-make: Unclosed variable expression, expecting '}' for modifier "On" of variable "NUMBERS" with value "1 2 3 4 5 6 7 8 9 10"
-make: Unclosed variable expression, expecting '}' for modifier "Onr" of variable "NUMBERS" with value "10 9 8 7 6 5 4 3 2 1"
-make: Bad modifier ":Oxn" for variable "NUMBERS"
-make: "varmod-order.mk" line 29: Malformed conditional (${NUMBERS:Oxn})
-make: Bad modifier ":On_typo" for variable "NUMBERS"
-make: "varmod-order.mk" line 39: Malformed conditional (${NUMBERS:On_typo})
-make: Bad modifier ":Onr_typo" for variable "NUMBERS"
-make: "varmod-order.mk" line 48: Malformed conditional (${NUMBERS:Onr_typo})
-make: Bad modifier ":Orn_typo" for variable "NUMBERS"
-make: "varmod-order.mk" line 57: Malformed conditional (${NUMBERS:Orn_typo})
-make: Bad modifier ":Onn" for variable "NUMBERS"
-make: "varmod-order.mk" line 68: Malformed conditional (${NUMBERS:Onn})
-make: Bad modifier ":Onrr" for variable "NUMBERS"
-make: "varmod-order.mk" line 77: Malformed conditional (${NUMBERS:Onrr})
-make: Bad modifier ":Orrn" for variable "NUMBERS"
-make: "varmod-order.mk" line 86: Malformed conditional (${NUMBERS:Orrn})
+make: varmod-order.mk:14: Unknown modifier ":OX"
+ while evaluating variable "WORDS" with value "one two three four five six seven eight nine ten"
+make: varmod-order.mk:17: Unknown modifier ":OxXX"
+ while evaluating variable "WORDS" with value "one two three four five six seven eight nine ten"
+make: varmod-order.mk:20: Unclosed expression, expecting "}" for modifier "O"
+ while evaluating variable "WORDS" with value "eight five four nine one seven six ten three two"
+make: varmod-order.mk:22: Unclosed expression, expecting "}" for modifier "On"
+ while evaluating variable "NUMBERS" with value "1 2 3 4 5 6 7 8 9 10"
+make: varmod-order.mk:24: Unclosed expression, expecting "}" for modifier "Onr"
+ while evaluating variable "NUMBERS" with value "10 9 8 7 6 5 4 3 2 1"
+make: varmod-order.mk:30: Unknown modifier ":Oxn"
+ while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2"
+make: varmod-order.mk:39: Unknown modifier ":On_typo"
+ while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2"
+make: varmod-order.mk:48: Unknown modifier ":Onr_typo"
+ while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2"
+make: varmod-order.mk:57: Unknown modifier ":Orn_typo"
+ while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2"
+make: varmod-order.mk:68: Unknown modifier ":Onn"
+ while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2"
+make: varmod-order.mk:77: Unknown modifier ":Onrr"
+ while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2"
+make: varmod-order.mk:86: Unknown modifier ":Orrn"
+ while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varmod-order.mk b/contrib/bmake/unit-tests/varmod-order.mk
index c6028fc10abd..f4fa5d10cdf8 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.8 2022/01/15 12:35:18 rillig Exp $
+# $NetBSD: varmod-order.mk,v 1.20 2025/06/28 22:39:29 rillig Exp $
#
# Tests for the :O variable modifier and its variants, which either sort the
# words of the value or shuffle them.
@@ -10,22 +10,23 @@ NUMBERS= 8 5 4 9 1 7 6 10 3 2 # in English alphabetical order
. error ${WORDS:O}
.endif
-# Unknown modifier "OX"
+# expect+1: Unknown modifier ":OX"
_:= ${WORDS:OX}
-# Unknown modifier "OxXX"
+# expect+1: Unknown modifier ":OxXX"
_:= ${WORDS:OxXX}
-# Missing closing brace, to cover the error handling code.
+# expect+1: Unclosed expression, expecting "}" for modifier "O"
_:= ${WORDS:O
+# expect+1: Unclosed expression, expecting "}" for modifier "On"
_:= ${NUMBERS:On
+# expect+1: Unclosed expression, expecting "}" for modifier "Onr"
_:= ${NUMBERS:Onr
# Shuffling numerically doesn't make sense, so don't allow 'x' and 'n' to be
# combined.
#
-# expect: make: Bad modifier ":Oxn" for variable "NUMBERS"
-# expect+1: Malformed conditional (${NUMBERS:Oxn})
+# expect+1: Unknown modifier ":Oxn"
.if ${NUMBERS:Oxn}
. error
.else
@@ -33,9 +34,8 @@ _:= ${NUMBERS:Onr
.endif
# Extra characters after ':On' are detected and diagnosed.
-# TODO: Add line number information to the "Bad modifier" diagnostic.
#
-# expect: make: Bad modifier ":On_typo" for variable "NUMBERS"
+# expect+1: Unknown modifier ":On_typo"
.if ${NUMBERS:On_typo}
. error
.else
@@ -44,7 +44,7 @@ _:= ${NUMBERS:Onr
# Extra characters after ':Onr' are detected and diagnosed.
#
-# expect: make: Bad modifier ":Onr_typo" for variable "NUMBERS"
+# expect+1: Unknown modifier ":Onr_typo"
.if ${NUMBERS:Onr_typo}
. error
.else
@@ -53,7 +53,7 @@ _:= ${NUMBERS:Onr
# Extra characters after ':Orn' are detected and diagnosed.
#
-# expect: make: Bad modifier ":Orn_typo" for variable "NUMBERS"
+# expect+1: Unknown modifier ":Orn_typo"
.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: make: Bad modifier ":Onn" for variable "NUMBERS"
+# expect+1: Unknown modifier ":Onn"
.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: make: Bad modifier ":Onrr" for variable "NUMBERS"
+# expect+1: Unknown modifier ":Onrr"
.if ${NUMBERS:Onrr}
. error
.else
@@ -82,11 +82,19 @@ _:= ${NUMBERS:Onr
# Repeating the 'r' is not supported as well, for the same reasons as above.
#
-# expect: make: Bad modifier ":Orrn" for variable "NUMBERS"
+# expect+1: Unknown modifier ":Orrn"
.if ${NUMBERS:Orrn}
. error
.else
. error
.endif
+
+# If a modifier that starts with ':O' is not one of the known sort or shuffle
+# forms, fall back to the SysV modifier.
+SWITCH= On
+.if ${SWITCH:On=Off} != "Off"
+. error
+.endif
+
all:
diff --git a/contrib/bmake/unit-tests/varmod-path.mk b/contrib/bmake/unit-tests/varmod-path.mk
index ebbf755ddbec..25d4e3899b99 100644
--- a/contrib/bmake/unit-tests/varmod-path.mk
+++ b/contrib/bmake/unit-tests/varmod-path.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-path.mk,v 1.3 2020/08/23 08:10:49 rillig Exp $
+# $NetBSD: varmod-path.mk,v 1.4 2023/05/10 15:53:32 rillig Exp $
#
# Tests for the :P variable modifier, which looks up the path for a given
# target.
@@ -7,11 +7,12 @@
# as of 2020-08-23 it is nevertheless resolved to a path. This is probably
# unintended.
#
-# The real target is located in a subdirectory, and its full path is returned.
-# If it had been in the current directory, the difference between its path and
-# its name would not be visible.
+# In this test, the real target is located in a subdirectory, and its full
+# path is returned. If it had been in the current directory, the difference
+# between its path and its name would not be visible.
#
-# The enoent target does not exist, therefore the target name is returned.
+# The enoent target does not exist, therefore the plain name of the target
+# is returned.
.MAIN: all
@@ -20,7 +21,8 @@ _!= mkdir varmod-path.subdir
_!= > varmod-path.subdir/varmod-path.phony
_!= > varmod-path.subdir/varmod-path.real
-# To have an effect, this .PATH declaration must be after the directory is created.
+# To have an effect, this .PATH declaration must be processed after the
+# directory has been created.
.PATH: varmod-path.subdir
varmod-path.phony: .PHONY
diff --git a/contrib/bmake/unit-tests/varmod-quote-dollar.exp b/contrib/bmake/unit-tests/varmod-quote-dollar.exp
index 4346401c5a9d..f12b0280e6f7 100644
--- a/contrib/bmake/unit-tests/varmod-quote-dollar.exp
+++ b/contrib/bmake/unit-tests/varmod-quote-dollar.exp
@@ -1,2 +1,4 @@
!"#$$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~
+!"#$$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~
+ !"#$$%&'()*+,-./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 3316b04bed1e..8e68282c536d 100644
--- a/contrib/bmake/unit-tests/varmod-quote-dollar.mk
+++ b/contrib/bmake/unit-tests/varmod-quote-dollar.mk
@@ -1,10 +1,17 @@
-# $NetBSD: varmod-quote-dollar.mk,v 1.3 2022/01/22 17:10:51 rillig Exp $
+# $NetBSD: varmod-quote-dollar.mk,v 1.4 2022/05/08 10:14:40 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.
+# The newline and space characters at the beginning of this string are passed
+# to the child make. When the child make parses the variable assignment, it
+# discards the leading space characters.
ASCII_CHARS= ${.newline} !"\#$$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~
all:
- @${MAKE} -r -f /dev/null CHARS=${ASCII_CHARS:q} -V CHARS
+ @${MAKE} -r -f /dev/null \
+ CHARS=${ASCII_CHARS:q} \
+ TWICE=${ASCII_CHARS:q}${ASCII_CHARS:q} \
+ -V CHARS \
+ -V TWICE
diff --git a/contrib/bmake/unit-tests/varmod-range.exp b/contrib/bmake/unit-tests/varmod-range.exp
index f4ada11ebde6..b98865a4084a 100644
--- a/contrib/bmake/unit-tests/varmod-range.exp
+++ b/contrib/bmake/unit-tests/varmod-range.exp
@@ -1,13 +1,15 @@
-make: "varmod-range.mk" line 53: Invalid number "x}Rest" != "Rest"" for ':range' modifier
-make: "varmod-range.mk" line 53: Malformed conditional ("${:U:range=x}Rest" != "Rest")
-make: "varmod-range.mk" line 62: Unknown modifier "x0"
-make: "varmod-range.mk" line 62: Malformed conditional ("${:U:range=0x0}Rest" != "Rest")
-make: "varmod-range.mk" line 78: Unknown modifier "rang"
-make: "varmod-range.mk" line 78: Malformed conditional ("${a b c:L:rang}Rest" != "Rest")
-make: "varmod-range.mk" line 85: Unknown modifier "rango"
-make: "varmod-range.mk" line 85: Malformed conditional ("${a b c:L:rango}Rest" != "Rest")
-make: "varmod-range.mk" line 92: Unknown modifier "ranger"
-make: "varmod-range.mk" line 92: Malformed conditional ("${a b c:L:ranger}Rest" != "Rest")
+make: varmod-range.mk:43: Variable "" is undefined
+ while evaluating "${:range=5} != """ with value "1 2 3 4 5"
+make: varmod-range.mk:66: Invalid number "x}Rest" != "Rest"" for modifier ":range"
+ while evaluating "${:U:range=x}Rest" != "Rest"" with value ""
+make: varmod-range.mk:76: Unknown modifier ":x0"
+ while evaluating "${:U:range=0x0}Rest" != "Rest"" with value "1"
+make: varmod-range.mk:93: Unknown modifier ":rang"
+ while evaluating variable "a b c" with value "a b c"
+make: varmod-range.mk:101: Unknown modifier ":rango"
+ while evaluating variable "a b c" with value "a b c"
+make: varmod-range.mk:109: Unknown modifier ":ranger"
+ while evaluating variable "a b c" with value "a b c"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varmod-range.mk b/contrib/bmake/unit-tests/varmod-range.mk
index d63525248e58..69dcf6ad1a7d 100644
--- a/contrib/bmake/unit-tests/varmod-range.mk
+++ b/contrib/bmake/unit-tests/varmod-range.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-range.mk,v 1.7 2020/11/01 14:36:25 rillig Exp $
+# $NetBSD: varmod-range.mk,v 1.19 2025/06/28 22:39:29 rillig Exp $
#
# Tests for the :range variable modifier, which generates sequences
# of integers from the given range.
@@ -7,7 +7,7 @@
# modword.mk
# The :range modifier generates a sequence of integers, one number per
-# word of the variable expression's value.
+# word of the expression's value.
.if ${a b c:L:range} != "1 2 3"
. error
.endif
@@ -19,20 +19,32 @@
.endif
# The :range modifier takes the number of words from the value of the
-# variable expression. If that expression is undefined, the range is
+# expression. If that expression is undefined, the range is
# undefined as well. This should not come as a surprise.
.if "${:range}" != ""
. error
.endif
+# An empty expression results in a sequence of a single number, even though
+# the expression contains 0 words.
+.if ${:U:range} != "1"
+. error
+.endif
+
# The :range modifier can be given a parameter, which makes the generated
-# range independent from the value or the name of the variable expression.
-#
-# XXX: As of 2020-09-27, the :range=... modifier does not turn the undefined
-# expression into a defined one. This looks like an oversight.
+# range independent from the value or the name of the expression.
.if "${:range=5}" != ""
. error
.endif
+# XXX: As of 2023-12-17, the ':range=n' modifier does not turn the undefined
+# expression into a defined one, even though it does not depend on the value
+# of the expression. This looks like an oversight.
+# expect+1: Variable "" is undefined
+.if ${:range=5} != ""
+. error
+.else
+. error
+.endif
# Negative ranges don't make sense.
# As of 2020-11-01, they are accepted though, using up all available memory.
@@ -50,6 +62,7 @@
#
# Since 2020-11-01, the parser issues a more precise "Invalid number" error
# instead.
+# expect+1: Invalid number "x}Rest" != "Rest"" for modifier ":range"
.if "${:U:range=x}Rest" != "Rest"
. error
.else
@@ -59,6 +72,7 @@
# The upper limit of the range must always be given in decimal.
# This parse error stops at the 'x', trying to parse it as a variable
# modifier.
+# expect+1: Unknown modifier ":x0"
.if "${:U:range=0x0}Rest" != "Rest"
. error
.else
@@ -75,6 +89,7 @@
#.endif
# modifier name too short
+# expect+1: Unknown modifier ":rang"
.if "${a b c:L:rang}Rest" != "Rest"
. error
.else
@@ -82,6 +97,7 @@
.endif
# misspelled modifier name
+# expect+1: Unknown modifier ":rango"
.if "${a b c:L:rango}Rest" != "Rest"
. error
.else
@@ -89,6 +105,7 @@
.endif
# modifier name too long
+# expect+1: Unknown modifier ":ranger"
.if "${a b c:L:ranger}Rest" != "Rest"
. error
.else
diff --git a/contrib/bmake/unit-tests/varmod-remember.mk b/contrib/bmake/unit-tests/varmod-remember.mk
index 403811759672..e92b2d2c4012 100644
--- a/contrib/bmake/unit-tests/varmod-remember.mk
+++ b/contrib/bmake/unit-tests/varmod-remember.mk
@@ -1,30 +1,62 @@
-# $NetBSD: varmod-remember.mk,v 1.6 2021/03/14 17:27:27 rillig Exp $
+# $NetBSD: varmod-remember.mk,v 1.9 2023/02/09 22:21:57 rillig Exp $
#
-# Tests for the :_ modifier, which saves the current variable value
+# Tests for the :_ modifier, which saves the current expression value
# in the _ variable or another, to be used later again.
-.if ${1 2 3:L:_:@var@${_}@} != "1 2 3 1 2 3 1 2 3"
+
+# The ':_' modifier is typically used in situations where the value of an
+# expression is needed at the same time as a sequence of numbers. In these
+# cases, the value of the expression is saved in the temporary variable '_',
+# from where it is taken later in the same expression.
+ABC= ${A B C:L:_:range:@i@$i=${_:[$i]}@}
+DEF= ${D E F:L:_:range:@i@$i=${_:[$i]}@}
+GHI= ${G H I:L:_:range:@i@$i=${_:[$i]}@}
+
+ABC.global:= ${ABC} # is evaluated in the global scope
+.if ${ABC.global} != "1=A 2=B 3=C"
+. error
+.endif
+
+.if ${DEF} != "1=D 2=E 3=F" # is evaluated in the command line scope
+. error
+.endif
+
+# Before var.c 1.1040 from 2023-02-09, the temporary variable '_' was placed
+# in the scope of the current evaluation, which meant that after the first
+# ':_' modifier had been evaluated in command line scope, all further
+# evaluations in global scope could not overwrite the variable '_' anymore,
+# as the command line scope takes precedence over the global scope.
+# The expression ${GHI} therefore evaluated to '1=D 2=E 3=F', reusing the
+# value of '_' from the previous evaluation in command line scope.
+GHI.global:= ${GHI} # is evaluated in the global scope
+.if ${GHI.global} != "1=G 2=H 3=I"
. error
.endif
+
# In the parameterized form, having the variable name on the right side of
-# the = assignment operator is confusing. In almost all other situations
-# the variable name is on the left-hand side of the = operator. Luckily
-# this modifier is only rarely needed.
+# the = assignment operator looks confusing. In almost all other situations,
+# the variable name is on the left-hand side of the = operator, therefore
+# '_=SAVED' looks like it would copy 'SAVED' to '_'. Luckily, this modifier
+# is only rarely needed.
.if ${1 2 3:L:@var@${var:_=SAVED:}@} != "1 2 3"
. error
.elif ${SAVED} != "3"
. error
.endif
-# The ':_' modifier takes a variable name as optional argument. This variable
-# name can refer to other variables, though this was rather an implementation
-# oversight than an intended feature. The variable name stops at the first
-# '}' or ')' and thus cannot use the usual form ${VARNAME} of long variable
-# names.
+
+# The ':_' modifier takes a variable name as optional argument. Before var.c
+# 1.867 from 2021-03-14, this variable name could refer to other variables,
+# such as in 'VAR.$p'. It was not possible to refer to 'VAR.${param}' though,
+# as that form caused a parse error. The cause for the parse error in
+# '${...:_=VAR.${param}}' is that the variable name is parsed in an ad-hoc
+# manner, stopping at the first ':', ')' or '}', without taking any nested
+# expressions into account. Due to this inconsistency that short expressions
+# are possible but long expressions aren't, the name of the temporary variable
+# is no longer expanded.
#
-# Because of all these edge-casey conditions, this "feature" has been removed
-# in var.c 1.867 from 2021-03-14.
+# TODO: Warn about the unusual variable name '$S'.
S= INDIRECT_VARNAME
.if ${value:L:@var@${var:_=$S}@} != "value"
. error
@@ -32,4 +64,15 @@ S= INDIRECT_VARNAME
. error
.endif
+
+# When a variable using ':_' refers to another variable that also uses ':_',
+# the value of the temporary variable '_' from the inner expression leaks into
+# the evaluation of the outer expression. If the expressions were evaluated
+# independently, the last word of the result would be outer_='outer' instead.
+INNER= ${inner:L:_:@i@$i inner_='$_'@}
+OUTER= ${outer:L:_:@o@$o ${INNER} outer_='$_'@}
+.if ${OUTER} != "outer inner inner_='inner' outer_='inner'"
+.endif
+
+
all:
diff --git a/contrib/bmake/unit-tests/varmod-select-words.exp b/contrib/bmake/unit-tests/varmod-select-words.exp
index 02e9974c02d6..4ac95ead0852 100644
--- a/contrib/bmake/unit-tests/varmod-select-words.exp
+++ b/contrib/bmake/unit-tests/varmod-select-words.exp
@@ -1,5 +1,7 @@
-make: Bad modifier ":[]" for variable "LIST"
-LIST:[]="" is an error
+make: Invalid modifier ":[]"
+ while evaluating variable "LIST" with value "one two three four five six"
+ in command "@echo 'LIST:[]="${LIST:[]}" is an error'"
+ in target "mod-squarebrackets-empty"
LIST:[0]="one two three four five six"
LIST:[0x0]="one two three four five six"
LIST:[000]="one two three four five six"
@@ -37,18 +39,26 @@ 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
+make: Invalid modifier ":[1.]"
+ while evaluating variable "LIST" with value "one two three four five six"
+ in command "@echo 'LIST:[1.]="${LIST:[1.]}" is an error'"
+ in target "mod-squarebrackets-n-error-1"
+make: Extra text after "[1]"
+ while evaluating variable "LIST" with value "one two three four five six"
+ in command "@echo 'LIST:[1].="${LIST:[1].}" is an error'"
+ in target "mod-squarebrackets-n-error-2"
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
+make: Invalid modifier ":[-]"
+ while evaluating variable "LIST" with value "one two three four five six"
+ in command "@echo 'LIST:[-]="${LIST:[-]}" is an error'"
+ in target "mod-squarebrackets-n-error-3"
+make: Invalid modifier ":[--]"
+ while evaluating variable "LIST" with value "one two three four five six"
+ in command "@echo 'LIST:[--]="${LIST:[--]}" is an error'"
+ in target "mod-squarebrackets-n-error-4"
LIST:[-1]="six"
LIST:[-2]="five"
LIST:[-6]="one"
@@ -67,23 +77,35 @@ 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
+make: Invalid modifier ":[1.]"
+ while evaluating variable "LIST" with value "one two three four five six"
+ in command "@echo 'LIST:[1.]="${LIST:[1.]}" is an error'"
+ in target "mod-squarebrackets-start-end-error-1"
+make: Invalid modifier ":[1..]"
+ while evaluating variable "LIST" with value "one two three four five six"
+ in command "@echo 'LIST:[1..]="${LIST:[1..]}" is an error'"
+ in target "mod-squarebrackets-start-end-error-2"
+make: Invalid modifier ":[1.. ]"
+ while evaluating variable "LIST" with value "one two three four five six"
+ in command "@echo 'LIST:[1.. ]="${LIST:[1.. ]}" is an error'"
+ in target "mod-squarebrackets-start-end-error-3"
LIST:[1..1]="one"
-make: Bad modifier ":[1..1.]" for variable "LIST"
-LIST:[1..1.]="" is an error
+make: Invalid modifier ":[1..1.]"
+ while evaluating variable "LIST" with value "one two three four five six"
+ in command "@echo 'LIST:[1..1.]="${LIST:[1..1.]}" is an error'"
+ in target "mod-squarebrackets-start-end-error-4"
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
+make: Invalid modifier ":[0..1]"
+ while evaluating variable "LIST" with value "one two three four five six"
+ in command "@echo 'LIST:[0..1]="${LIST:[0..1]}" is an error'"
+ in target "mod-squarebrackets-start-end-error-5"
+make: Invalid modifier ":[-1..0]"
+ while evaluating variable "LIST" with value "one two three four five six"
+ in command "@echo 'LIST:[-1..0]="${LIST:[-1..0]}" is an error'"
+ in target "mod-squarebrackets-start-end-error-6"
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"
@@ -97,8 +119,10 @@ 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
+make: Invalid modifier ":[]"
+ while evaluating variable "LIST" with value "one two three four five six"
+ in command "@echo 'LIST:[$${EMPTY}]="${LIST:[${EMPTY}]}" is an error'"
+ in target "mod-squarebrackets-nested-error-1"
LIST:[${LONGLIST:[21]:S/2//}]="one"
LIST:[${LIST:[#]}]="six"
LIST:[${LIST:[${HASH}]}]="six"
@@ -123,4 +147,4 @@ 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
+exit status 2
diff --git a/contrib/bmake/unit-tests/varmod-select-words.mk b/contrib/bmake/unit-tests/varmod-select-words.mk
index 910b67a24e39..1217bb3c94b5 100644
--- a/contrib/bmake/unit-tests/varmod-select-words.mk
+++ b/contrib/bmake/unit-tests/varmod-select-words.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-select-words.mk,v 1.4 2022/01/23 16:09:38 rillig Exp $
+# $NetBSD: varmod-select-words.mk,v 1.7 2025/03/30 01:27:13 rillig Exp $
#
# Tests for the :[...] variable modifier, which selects a single word
# or a range of words from a variable.
@@ -24,15 +24,36 @@ ZERO= 0
ONE= 1
MINUSONE= -1
-mod-squarebrackets: mod-squarebrackets-0-star-at \
+mod-squarebrackets: \
+ mod-squarebrackets-empty \
+ mod-squarebrackets-0-star-at \
mod-squarebrackets-hash \
- mod-squarebrackets-n \
- mod-squarebrackets-start-end \
- mod-squarebrackets-nested \
+ mod-squarebrackets-n-ok-1 \
+ mod-squarebrackets-n-error-1 \
+ mod-squarebrackets-n-error-2 \
+ mod-squarebrackets-n-ok-2 \
+ mod-squarebrackets-n-error-3 \
+ mod-squarebrackets-n-error-4 \
+ mod-squarebrackets-n-ok-3 \
+ mod-squarebrackets-start-end-error-1 \
+ mod-squarebrackets-start-end-error-2 \
+ mod-squarebrackets-start-end-error-3 \
+ mod-squarebrackets-start-end-ok-1 \
+ mod-squarebrackets-start-end-error-4 \
+ mod-squarebrackets-start-end-ok-2 \
+ mod-squarebrackets-start-end-error-5 \
+ mod-squarebrackets-start-end-error-6 \
+ mod-squarebrackets-start-end-ok-3 \
+ mod-squarebrackets-nested-ok-1 \
+ mod-squarebrackets-nested-error-1 \
+ mod-squarebrackets-nested-ok-2 \
mod-squarebrackets-space
-mod-squarebrackets-0-star-at:
+mod-squarebrackets-empty:
+# expect: make: Invalid modifier ":[]"
@echo 'LIST:[]="${LIST:[]}" is an error'
+
+mod-squarebrackets-0-star-at:
@echo 'LIST:[0]="${LIST:[0]}"'
@echo 'LIST:[0x0]="${LIST:[0x0]}"'
@echo 'LIST:[000]="${LIST:[000]}"'
@@ -66,7 +87,7 @@ mod-squarebrackets-hash:
@echo 'LIST:[1]:[#]="${LIST:[1]:[#]}"'
@echo 'LIST:[1..3]:[#]="${LIST:[1..3]:[#]}"'
-mod-squarebrackets-n:
+mod-squarebrackets-n-ok-1:
@echo 'EMPTY:[1]="${EMPTY:[1]}"'
@echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"'
@echo 'ESCAPEDSPACE:[1]="${ESCAPEDSPACE:[1]}"'
@@ -74,14 +95,24 @@ mod-squarebrackets-n:
@echo 'REALLYSPACE:[1]="${REALLYSPACE:[1]}" == "" ?'
@echo 'REALLYSPACE:[*]:[1]="${REALLYSPACE:[*]:[1]}" == " " ?'
@echo 'LIST:[1]="${LIST:[1]}"'
+mod-squarebrackets-n-error-1:
+# expect: make: Invalid modifier ":[1.]"
@echo 'LIST:[1.]="${LIST:[1.]}" is an error'
+mod-squarebrackets-n-error-2:
+# expect: make: Extra text after "[1]"
@echo 'LIST:[1].="${LIST:[1].}" is an error'
+mod-squarebrackets-n-ok-2:
@echo 'LIST:[2]="${LIST:[2]}"'
@echo 'LIST:[6]="${LIST:[6]}"'
@echo 'LIST:[7]="${LIST:[7]}"'
@echo 'LIST:[999]="${LIST:[999]}"'
+mod-squarebrackets-n-error-3:
+# expect: make: Invalid modifier ":[-]"
@echo 'LIST:[-]="${LIST:[-]}" is an error'
+mod-squarebrackets-n-error-4:
+# expect: make: Invalid modifier ":[--]"
@echo 'LIST:[--]="${LIST:[--]}" is an error'
+mod-squarebrackets-n-ok-3:
@echo 'LIST:[-1]="${LIST:[-1]}"'
@echo 'LIST:[-2]="${LIST:[-2]}"'
@echo 'LIST:[-6]="${LIST:[-6]}"'
@@ -101,25 +132,39 @@ mod-squarebrackets-n:
@echo 'LIST:[*]:C/ /,/:[@]:[2]="${LIST:[*]:C/ /,/:[@]:[2]}"'
@echo 'LONGLIST:[012..0x12]="${LONGLIST:[012..0x12]}"'
-mod-squarebrackets-start-end:
+mod-squarebrackets-start-end-error-1:
+# expect: make: Invalid modifier ":[1.]"
@echo 'LIST:[1.]="${LIST:[1.]}" is an error'
+mod-squarebrackets-start-end-error-2:
+# expect: make: Invalid modifier ":[1..]"
@echo 'LIST:[1..]="${LIST:[1..]}" is an error'
+mod-squarebrackets-start-end-error-3:
+# expect: make: Invalid modifier ":[1.. ]"
@echo 'LIST:[1.. ]="${LIST:[1.. ]}" is an error'
+mod-squarebrackets-start-end-ok-1:
@echo 'LIST:[1..1]="${LIST:[1..1]}"'
+mod-squarebrackets-start-end-error-4:
+# expect: make: Invalid modifier ":[1..1.]"
@echo 'LIST:[1..1.]="${LIST:[1..1.]}" is an error'
+mod-squarebrackets-start-end-ok-2:
@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]}"'
+mod-squarebrackets-start-end-error-5:
+# expect: make: Invalid modifier ":[0..1]"
@echo 'LIST:[0..1]="${LIST:[0..1]}" is an error'
+mod-squarebrackets-start-end-error-6:
+# expect: make: Invalid modifier ":[-1..0]"
@echo 'LIST:[-1..0]="${LIST:[-1..0]}" is an error'
+mod-squarebrackets-start-end-ok-3:
@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:
+mod-squarebrackets-nested-ok-1:
@echo 'HASH="${HASH}" == "#" ?'
@echo 'LIST:[$${HASH}]="${LIST:[${HASH}]}"'
@echo 'LIST:[$${ZERO}]="${LIST:[${ZERO}]}"'
@@ -128,7 +173,10 @@ mod-squarebrackets-nested:
@echo 'LIST:[$${MINUSONE}]="${LIST:[${MINUSONE}]}"'
@echo 'LIST:[$${STAR}]="${LIST:[${STAR}]}"'
@echo 'LIST:[$${AT}]="${LIST:[${AT}]}"'
+mod-squarebrackets-nested-error-1:
+# expect: make: Invalid modifier ":[]"
@echo 'LIST:[$${EMPTY}]="${LIST:[${EMPTY}]}" is an error'
+mod-squarebrackets-nested-ok-2:
@echo 'LIST:[$${LONGLIST:[21]:S/2//}]="${LIST:[${LONGLIST:[21]:S/2//}]}"'
@echo 'LIST:[$${LIST:[#]}]="${LIST:[${LIST:[#]}]}"'
@echo 'LIST:[$${LIST:[$${HASH}]}]="${LIST:[${LIST:[${HASH}]}]}"'
diff --git a/contrib/bmake/unit-tests/varmod-shell.exp b/contrib/bmake/unit-tests/varmod-shell.exp
index 208ef953728b..7f93e8354a1e 100644
--- a/contrib/bmake/unit-tests/varmod-shell.exp
+++ b/contrib/bmake/unit-tests/varmod-shell.exp
@@ -1,12 +1,12 @@
-make: "echo word; false" returned non-zero status
-make: "echo word; false" returned non-zero status
+make: varmod-shell.mk:25: warning: Command "echo word; (exit 13)" exited with status 13
+make: varmod-shell.mk:29: warning: Command "echo word; (exit 13)" exited with status 13
Global: _ = # (empty)
-Var_Parse: ${:!echo word; ${:Ufalse}!} (eval-keep-dollar-and-undefined)
+Var_Parse: ${:!echo word; ${:U(exit 13)}!} (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)
+Modifier part: "echo word; (exit 13)"
+Capturing the output of command "echo word; (exit 13)"
+make: varmod-shell.mk:36: warning: Command "echo word; (exit 13)" exited with status 13
+Result of ${:!echo word; ${:U(exit 13)}!} 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
diff --git a/contrib/bmake/unit-tests/varmod-shell.mk b/contrib/bmake/unit-tests/varmod-shell.mk
index d449709cee0f..5f614d0c7f3c 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.7 2022/01/10 20:32:29 rillig Exp $
+# $NetBSD: varmod-shell.mk,v 1.11 2024/08/29 20:20:37 rillig Exp $
#
# Tests for the ':!cmd!' variable modifier, which runs the shell command
# given by the variable modifier and returns its output.
@@ -21,16 +21,20 @@
# 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.
-.if ${:!echo word; false!} != "word"
+# expect+1: warning: Command "echo word; (exit 13)" exited with status 13
+.if ${:!echo word; (exit 13)!} != "word"
. error
.endif
-.if ${:Uprevious value:!echo word; false!} != "word"
+# expect+1: warning: Command "echo word; (exit 13)" exited with status 13
+.if ${:Uprevious value:!echo word; (exit 13)!} != "word"
. error
.endif
-.MAKEFLAGS: -dv # to see the actual command
-_:= ${:!echo word; ${:Ufalse}!}
+.MAKEFLAGS: -dv # to see the "Capturing" debug output
+# expect+1: warning: Command "echo word; (exit 13)" exited with status 13
+_:= ${:!echo word; ${:U(exit 13)}!}
.MAKEFLAGS: -d0
+
all:
diff --git a/contrib/bmake/unit-tests/varmod-subst-regex.exp b/contrib/bmake/unit-tests/varmod-subst-regex.exp
index a09046ef764c..816b02248591 100644
--- a/contrib/bmake/unit-tests/varmod-subst-regex.exp
+++ b/contrib/bmake/unit-tests/varmod-subst-regex.exp
@@ -1,27 +1,67 @@
make: Regex compilation error: (details omitted)
-mod-regex-compile-error: C,word,____,:Q}.
+ while evaluating "${:Uword1 word2:C,****,____,g:C,word,____,:Q}." with value "word1 word2"
+ in command "@echo $@: ${:Uword1 word2:C,****,____,g:C,word,____,:Q}."
+ in target "mod-regex-compile-error"
make: No subexpression \1
+ while evaluating "${:U1 23 456:C,..,\1\1,:Q}" with value "1 23 456"
+ in command "@echo $@:11-missing:${:U1 23 456:C,..,\1\1,:Q}"
+ in target "mod-regex-limits-1"
make: No subexpression \1
+ while evaluating "${:U1 23 456:C,..,\1\1,:Q}" with value "1 23 456"
+ in command "@echo $@:11-missing:${:U1 23 456:C,..,\1\1,:Q}"
+ in target "mod-regex-limits-1"
make: No subexpression \1
+ while evaluating "${:U1 23 456:C,..,\1\1,:Q}" with value "1 23 456"
+ in command "@echo $@:11-missing:${:U1 23 456:C,..,\1\1,:Q}"
+ in target "mod-regex-limits-1"
make: No subexpression \1
-mod-regex-limits:11-missing:1 6
-mod-regex-limits:11-ok:1 22 446
+ while evaluating "${:U1 23 456:C,..,\1\1,:Q}" with value "1 23 456"
+ in command "@echo $@:11-missing:${:U1 23 456:C,..,\1\1,:Q}"
+ in target "mod-regex-limits-1"
+mod-regex-limits-2:11-ok:1 22 446
make: No subexpression \2
+ while evaluating "${:U1 23 456:C,..,\2\2,:Q}" with value "1 23 456"
+ in command "@echo $@:22-missing:${:U1 23 456:C,..,\2\2,:Q}"
+ in target "mod-regex-limits-3"
make: No subexpression \2
+ while evaluating "${:U1 23 456:C,..,\2\2,:Q}" with value "1 23 456"
+ in command "@echo $@:22-missing:${:U1 23 456:C,..,\2\2,:Q}"
+ in target "mod-regex-limits-3"
make: No subexpression \2
+ while evaluating "${:U1 23 456:C,..,\2\2,:Q}" with value "1 23 456"
+ in command "@echo $@:22-missing:${:U1 23 456:C,..,\2\2,:Q}"
+ in target "mod-regex-limits-3"
make: No subexpression \2
-mod-regex-limits:22-missing:1 6
+ while evaluating "${:U1 23 456:C,..,\2\2,:Q}" with value "1 23 456"
+ in command "@echo $@:22-missing:${:U1 23 456:C,..,\2\2,:Q}"
+ in target "mod-regex-limits-3"
make: No subexpression \2
+ while evaluating "${:U1 23 456:C,(.).,\2\2,:Q}" with value "1 23 456"
+ in command "@echo $@:22-missing:${:U1 23 456:C,(.).,\2\2,:Q}"
+ in target "mod-regex-limits-4"
make: No subexpression \2
+ while evaluating "${:U1 23 456:C,(.).,\2\2,:Q}" with value "1 23 456"
+ in command "@echo $@:22-missing:${:U1 23 456:C,(.).,\2\2,:Q}"
+ in target "mod-regex-limits-4"
make: No subexpression \2
+ while evaluating "${:U1 23 456:C,(.).,\2\2,:Q}" with value "1 23 456"
+ in command "@echo $@:22-missing:${:U1 23 456:C,(.).,\2\2,:Q}"
+ in target "mod-regex-limits-4"
make: No subexpression \2
-mod-regex-limits:22-missing:1 6
-mod-regex-limits:22-ok:1 33 556
-mod-regex-limits:capture:ihgfedcbaabcdefghijABCDEFGHIJa0a1a2rest
+ while evaluating "${:U1 23 456:C,(.).,\2\2,:Q}" with value "1 23 456"
+ in command "@echo $@:22-missing:${:U1 23 456:C,(.).,\2\2,:Q}"
+ in target "mod-regex-limits-4"
+mod-regex-limits-5:22-ok:1 33 556
+mod-regex-limits-6:capture:ihgfedcbaabcdefghijABCDEFGHIJa0a1a2rest
make: Regex compilation error: (details omitted)
-mod-regex-errors:
-make: Unknown modifier "Z"
-mod-regex-errors: xy
+ while evaluating variable "UNDEF" with value "value"
+ in command "@echo $@: ${UNDEF:Uvalue:C,[,,}"
+ in target "mod-regex-errors-1"
+make: Unknown modifier ":Z"
+ while evaluating "${:U:Z}y,W}" with value ""
+ while evaluating variable "word" with value "word"
+ in command "@echo $@: ${word:L:C,.*,x${:U:Z}y,W}"
+ in target "mod-regex-errors-2"
unmatched-subexpression.ok: one one 2 3 5 8 one3 2one 34
make: No match for subexpression \2
unmatched-subexpression.1: ()()
diff --git a/contrib/bmake/unit-tests/varmod-subst-regex.mk b/contrib/bmake/unit-tests/varmod-subst-regex.mk
index 197691d73aad..bc04bc5fffb9 100644
--- a/contrib/bmake/unit-tests/varmod-subst-regex.mk
+++ b/contrib/bmake/unit-tests/varmod-subst-regex.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-subst-regex.mk,v 1.7 2021/06/21 08:17:39 rillig Exp $
+# $NetBSD: varmod-subst-regex.mk,v 1.12 2024/07/20 11:05:12 rillig Exp $
#
# Tests for the :C,from,to, variable modifier.
@@ -6,11 +6,11 @@
.MAKEFLAGS: -dL
all: mod-regex-compile-error
-all: mod-regex-limits
-all: mod-regex-errors
+all: mod-regex-limits-{1,2,3,4,5,6}
+all: mod-regex-errors-{1,2}
all: unmatched-subexpression
-# The variable expression expands to 4 words. Of these words, none matches
+# The expression expands to 4 words. Of these words, none matches
# the regular expression "a b" since these words don't contain any
# whitespace.
.if ${:Ua b b c:C,a b,,} != "a b b c"
@@ -18,7 +18,7 @@ all: unmatched-subexpression
.endif
# Using the '1' modifier does not change anything. The '1' modifier just
-# means to apply at most 1 replacement in the whole variable expression.
+# means to apply at most 1 replacement in the whole expression.
.if ${:Ua b b c:C,a b,,1} != "a b b c"
. error
.endif
@@ -84,9 +84,54 @@ all: unmatched-subexpression
. error
.endif
+
+# Like the ':S' modifier, the ':C' modifier matches on an expression
+# that contains no words at all, but only if the regular expression matches an
+# empty string, for example, when the regular expression is anchored at the
+# beginning or the end of the word. An unanchored regular expression that
+# matches the empty string is uncommon in practice, as it would match before
+# each character of the word.
+.if "<${:U:S,,unanchored,}> <${:U:C,.?,unanchored,}>" != "<> <unanchored>"
+. error
+.endif
+.if "<${:U:S,^,prefix,}> <${:U:C,^,prefix,}>" != "<prefix> <prefix>"
+. error
+.endif
+.if "<${:U:S,$,suffix,}> <${:U:C,$,suffix,}>" != "<suffix> <suffix>"
+. error
+.endif
+.if "<${:U:S,^$,whole,}> <${:U:C,^$,whole,}>" != "<whole> <whole>"
+. error
+.endif
+.if "<${:U:S,,unanchored,g}> <${:U:C,.?,unanchored,g}>" != "<> <unanchored>"
+. error
+.endif
+.if "<${:U:S,^,prefix,g}> <${:U:C,^,prefix,g}>" != "<prefix> <prefix>"
+. error
+.endif
+.if "<${:U:S,$,suffix,g}> <${:U:C,$,suffix,g}>" != "<suffix> <suffix>"
+. error
+.endif
+.if "<${:U:S,^$,whole,g}> <${:U:C,^$,whole,g}>" != "<whole> <whole>"
+. error
+.endif
+.if "<${:U:S,,unanchored,W}> <${:U:C,.?,unanchored,W}>" != "<> <unanchored>"
+. error
+.endif
+.if "<${:U:S,^,prefix,W}> <${:U:C,^,prefix,W}>" != "<prefix> <prefix>"
+. error
+.endif
+.if "<${:U:S,$,suffix,W}> <${:U:C,$,suffix,W}>" != "<suffix> <suffix>"
+. error
+.endif
+.if "<${:U:S,^$,whole,W}> <${:U:C,^$,whole,W}>" != "<whole> <whole>"
+. error
+.endif
+
+
# Multiple asterisks form an invalid regular expression. This produces an
# error message and (as of 2020-08-28) stops parsing in the middle of the
-# variable expression. The unparsed part of the expression is then copied
+# expression. The unparsed part of the expression is then copied
# verbatim to the output, which is unexpected and can lead to strange shell
# commands being run.
mod-regex-compile-error:
@@ -94,22 +139,28 @@ mod-regex-compile-error:
# These tests generate error messages but as of 2020-08-28 just continue
# parsing and execution as if nothing bad had happened.
-mod-regex-limits:
+mod-regex-limits-1:
@echo $@:11-missing:${:U1 23 456:C,..,\1\1,:Q}
+mod-regex-limits-2:
@echo $@:11-ok:${:U1 23 456:C,(.).,\1\1,:Q}
+mod-regex-limits-3:
@echo $@:22-missing:${:U1 23 456:C,..,\2\2,:Q}
+mod-regex-limits-4:
@echo $@:22-missing:${:U1 23 456:C,(.).,\2\2,:Q}
+mod-regex-limits-5:
@echo $@:22-ok:${:U1 23 456:C,(.)(.),\2\2,:Q}
+mod-regex-limits-6:
# The :C modifier only handles single-digit capturing groups,
- # which is more than enough for daily use.
+ # which is enough for all practical use cases.
@echo $@:capture:${:UabcdefghijABCDEFGHIJrest:C,(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.),\9\8\7\6\5\4\3\2\1\0\10\11\12,}
-mod-regex-errors:
+mod-regex-errors-1:
@echo $@: ${UNDEF:Uvalue:C,[,,}
+mod-regex-errors-2:
# If the replacement pattern produces a parse error because of an
# unknown modifier, the parse error is ignored in ParseModifierPart
- # and the faulty variable expression expands to "".
+ # and the faulty expression expands to "".
@echo $@: ${word:L:C,.*,x${:U:Z}y,W}
# In regular expressions with alternatives, not all capturing groups are
diff --git a/contrib/bmake/unit-tests/varmod-subst.exp b/contrib/bmake/unit-tests/varmod-subst.exp
index 97fa2e4f1491..8eec26e33ef5 100644
--- a/contrib/bmake/unit-tests/varmod-subst.exp
+++ b/contrib/bmake/unit-tests/varmod-subst.exp
@@ -45,8 +45,10 @@ mod-subst-delimiter:
1 two 3 tilde
mod-subst-chain:
A B c.
-make: Unknown modifier "i"
-.
+make: Unknown modifier ":i"
+ while evaluating "${:Uvalue:S,a,x,i}." with value "vxlue"
+ in command "@echo ${:Uvalue:S,a,x,i}."
+ in target "mod-subst-chain"
mod-subst-dollar:$1:
mod-subst-dollar:$2:
mod-subst-dollar:$3:
@@ -59,4 +61,4 @@ mod-subst-dollar:$40:
mod-subst-dollar:U8:
mod-subst-dollar:$$$$:
mod-subst-dollar:$$$good3
-exit status 0
+exit status 2
diff --git a/contrib/bmake/unit-tests/varmod-subst.mk b/contrib/bmake/unit-tests/varmod-subst.mk
index 763535ff835a..e9da303515b2 100644
--- a/contrib/bmake/unit-tests/varmod-subst.mk
+++ b/contrib/bmake/unit-tests/varmod-subst.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-subst.mk,v 1.9 2021/09/06 21:18:55 rillig Exp $
+# $NetBSD: varmod-subst.mk,v 1.17 2025/03/29 19:08:53 rillig Exp $
#
# Tests for the :S,from,to, variable modifier.
@@ -9,83 +9,118 @@ all: mod-subst-dollar
WORDS= sequences of letters
+# The empty pattern never matches anything, except if it is anchored at the
+# beginning or the end of the word.
.if ${WORDS:S,,,} != ${WORDS}
-. warning The empty pattern matches something.
+. error
.endif
+# The :S modifier flag '1' is applied exactly once.
.if ${WORDS:S,e,*,1} != "s*quences of letters"
-. warning The :S modifier flag '1' is not applied exactly once.
+. error
.endif
+# The :S modifier flag '1' is applied to the first occurrence, no matter if
+# the occurrence is in the first word or not.
.if ${WORDS:S,f,*,1} != "sequences o* letters"
-. warning The :S modifier flag '1' is only applied to the first word,\
- not to the first occurrence.
+. error
.endif
+# The :S modifier replaces every first match per word.
.if ${WORDS:S,e,*,} != "s*quences of l*tters"
-. warning The :S modifier does not replace every first match per word.
+. error
.endif
+# The :S modifier flag 'g' replaces every occurrence.
.if ${WORDS:S,e,*,g} != "s*qu*nc*s of l*tt*rs"
-. warning The :S modifier flag 'g' does not replace every occurrence.
+. error
.endif
+# The '^' in the search pattern anchors the pattern at the beginning of each
+# word, thereby matching a prefix.
.if ${WORDS:S,^sequ,occurr,} != "occurrences of letters"
-. warning The :S modifier fails for a short match anchored at the start.
+. error
.endif
+# The :S modifier with a '^' anchor replaces the whole word if that word is
+# exactly the pattern.
.if ${WORDS:S,^of,with,} != "sequences with letters"
-. warning The :S modifier fails for an exact match anchored at the start.
+. error
.endif
+# The :S modifier does not match if the pattern is longer than the word.
.if ${WORDS:S,^office,does not match,} != ${WORDS}
-. warning The :S modifier matches a too long pattern anchored at the start.
+. warning
.endif
+# The '$' in the search pattern anchors the pattern at the end of each word,
+# thereby matching a suffix.
.if ${WORDS:S,f$,r,} != "sequences or letters"
-. warning The :S modifier fails for a short match anchored at the end.
+. error
.endif
+# The :S modifier with a '$' anchor replaces at most one occurrence per word.
.if ${WORDS:S,s$,,} != "sequence of letter"
-. warning The :S modifier fails to replace one occurrence per word.
+. error
.endif
+# The :S modifier with a '$' anchor replaces the whole word if that word is
+# exactly the pattern.
.if ${WORDS:S,of$,,} != "sequences letters"
-. warning The :S modifier fails for an exact match anchored at the end.
+. error
.endif
+# The :S modifier with a '$' anchor and a pattern that is longer than a word
+# cannot match that word.
.if ${WORDS:S,eof$,,} != ${WORDS}
-. warning The :S modifier matches a too long pattern anchored at the end.
+. warning
.endif
+# The :S modifier with the '^' and '$' anchors matches an exact word.
.if ${WORDS:S,^of$,,} != "sequences letters"
-. warning The :S modifier does not match a word anchored at both ends.
+. error
.endif
+# The :S modifier with the '^' and '$' anchors does not match a word that
+# starts with the pattern but is longer than the pattern.
.if ${WORDS:S,^o$,,} != ${WORDS}
-. warning The :S modifier matches a prefix anchored at both ends.
+. error
.endif
+# The :S modifier with the '^' and '$' anchors does not match a word that ends
+# with the pattern but is longer than the pattern.
.if ${WORDS:S,^f$,,} != ${WORDS}
-. warning The :S modifier matches a suffix anchored at both ends.
+. error
.endif
+# The :S modifier with the '^' and '$' anchors does not match a word if the
+# pattern ends with the word but is longer than the word.
.if ${WORDS:S,^eof$,,} != ${WORDS}
-. warning The :S modifier matches a too long prefix anchored at both ends.
+. error
.endif
+# The :S modifier with the '^' and '$' anchors does not match a word if the
+# pattern starts with the word but is longer than the word.
.if ${WORDS:S,^office$,,} != ${WORDS}
-. warning The :S modifier matches a too long suffix anchored at both ends.
+. error
.endif
+# Except for the '^' and '$' anchors, the pattern does not contain any special
+# characters, so the '*' from the pattern would only match a literal '*' in a
+# word.
.if ${WORDS:S,*,replacement,} != ${WORDS}
-. error The '*' seems to be interpreted as a wildcard of some kind.
+. error
.endif
+# Except for the '^' and '$' anchors, the pattern does not contain any special
+# characters, so the '.' from the pattern would only match a literal '.' in a
+# word.
.if ${WORDS:S,.,replacement,} != ${WORDS}
-. error The '.' seems to be interpreted as a wildcard of some kind.
+. error
.endif
+# The '&' in the replacement is a placeholder for the text matched by the
+# pattern.
.if ${:Uvalue:S,^val,&,} != "value"
. error
.endif
@@ -99,6 +134,55 @@ WORDS= sequences of letters
. error
.endif
+
+# When a word is replaced with nothing, the remaining words are separated by a
+# single space, not two.
+.if ${1 2 3:L:S,2,,} != "1 3"
+. error
+.endif
+
+
+# In an empty expression, the ':S' modifier matches a single time, but only if
+# the search string is empty and anchored at either the beginning or the end
+# of the word.
+.if ${:U:S,,out-of-nothing,} != ""
+. error
+.endif
+.if ${:U:S,^,out-of-nothing,} != "out-of-nothing"
+. error
+.endif
+.if ${:U:S,$,out-of-nothing,} != "out-of-nothing"
+. error
+.endif
+.if ${:U:S,^$,out-of-nothing,} != "out-of-nothing"
+. error
+.endif
+.if ${:U:S,,out-of-nothing,g} != ""
+. error
+.endif
+.if ${:U:S,^,out-of-nothing,g} != "out-of-nothing"
+. error
+.endif
+.if ${:U:S,$,out-of-nothing,g} != "out-of-nothing"
+. error
+.endif
+.if ${:U:S,^$,out-of-nothing,g} != "out-of-nothing"
+. error
+.endif
+.if ${:U:S,,out-of-nothing,W} != ""
+. error
+.endif
+.if ${:U:S,^,out-of-nothing,W} != "out-of-nothing"
+. error
+.endif
+.if ${:U:S,$,out-of-nothing,W} != "out-of-nothing"
+. error
+.endif
+.if ${:U:S,^$,out-of-nothing,W} != "out-of-nothing"
+. error
+.endif
+
+
mod-subst:
@echo $@:
@echo :${:Ua b b c:S,a b,,:Q}:
@@ -168,6 +252,7 @@ mod-subst-chain:
# The error message is "make: Unknown modifier 'i'", which is
# kind of correct, although it is mixing the terms for variable
# modifiers with the matching modifiers.
+# expect: make: Unknown modifier ":i"
@echo ${:Uvalue:S,a,x,i}.
# No matter how many dollar signs there are, they all get merged
diff --git a/contrib/bmake/unit-tests/varmod-sun-shell.exp b/contrib/bmake/unit-tests/varmod-sun-shell.exp
index 7f661ff6e79e..e3dcdfa840ff 100644
--- a/contrib/bmake/unit-tests/varmod-sun-shell.exp
+++ b/contrib/bmake/unit-tests/varmod-sun-shell.exp
@@ -1,12 +1,12 @@
-make: "echo word; false" returned non-zero status
+make: varmod-sun-shell.mk:17: warning: Command "echo word; (exit 13)" exited with status 13
Global: _ = # (empty)
-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)
+Var_Parse: ${echo word; ${:U(exit 13)}:L:sh} (eval-keep-dollar-and-undefined)
+Evaluating modifier ${echo word; (exit 13):L} on value "" (eval-keep-dollar-and-undefined, undefined)
+Result of ${echo word; (exit 13):L} is "echo word; (exit 13)" (eval-keep-dollar-and-undefined, defined)
+Evaluating modifier ${echo word; (exit 13):s...} on value "echo word; (exit 13)" (eval-keep-dollar-and-undefined, defined)
+Capturing the output of command "echo word; (exit 13)"
+make: varmod-sun-shell.mk:24: warning: Command "echo word; (exit 13)" exited with status 13
+Result of ${echo word; (exit 13):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
diff --git a/contrib/bmake/unit-tests/varmod-sun-shell.mk b/contrib/bmake/unit-tests/varmod-sun-shell.mk
index 97acc5bd8c0f..8f6a5bc4cc05 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.2 2022/01/10 20:32:29 rillig Exp $
+# $NetBSD: varmod-sun-shell.mk,v 1.6 2024/08/29 20:20:37 rillig Exp $
#
# Tests for the :sh variable modifier, which runs the shell command
# given by the variable value and returns its output.
@@ -12,15 +12,17 @@
. error
.endif
-# If the command exits with non-zero, an error message is printed.
-# XXX: Processing continues as usual though.
-.if ${echo word; false:L:sh} != "word"
+# If the command exits with non-zero, a warning is printed.
+# expect+1: warning: Command "echo word; (exit 13)" exited with status 13
+.if ${echo word; (exit 13):L:sh} != "word"
. error
.endif
-.MAKEFLAGS: -dv # to see the actual command
-_:= ${echo word; ${:Ufalse}:L:sh}
+.MAKEFLAGS: -dv # to see the "Capturing" debug output
+# expect+1: warning: Command "echo word; (exit 13)" exited with status 13
+_:= ${echo word; ${:U(exit 13)}:L:sh}
.MAKEFLAGS: -d0
+
all:
diff --git a/contrib/bmake/unit-tests/varmod-sysv.exp b/contrib/bmake/unit-tests/varmod-sysv.exp
index 59275857f98a..d2396fc7d576 100644
--- a/contrib/bmake/unit-tests/varmod-sysv.exp
+++ b/contrib/bmake/unit-tests/varmod-sysv.exp
@@ -1,5 +1,5 @@
-make: Unfinished modifier for "word214" ('=' missing)
-make: "varmod-sysv.mk" line 214: Malformed conditional (${word214:L:from${:D=}to})
+make: varmod-sysv.mk:215: Unfinished modifier after "from${:D=}to}", expecting "="
+ while evaluating variable "word216" with value "word216"
word modifier result
'' = ""
suffix = "suffix"
@@ -145,6 +145,8 @@ pre-middle-suffix pre%ffix=NPre% "NPre-middle-su"
suffix pre%ffix=NPre%NS "suffix"
prefix pre%ffix=NPre%NS "prefix"
pre-middle-suffix pre%ffix=NPre%NS "NPre-middle-suNS"
+make: varmod-sysv.mk:259: Unfinished modifier after "$(})", expecting "}"
+ while evaluating variable "error" with value "error"
make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varmod-sysv.mk b/contrib/bmake/unit-tests/varmod-sysv.mk
index 712c1731717b..9209626cc214 100644
--- a/contrib/bmake/unit-tests/varmod-sysv.mk
+++ b/contrib/bmake/unit-tests/varmod-sysv.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-sysv.mk,v 1.14 2021/04/12 16:09:57 rillig Exp $
+# $NetBSD: varmod-sysv.mk,v 1.24 2025/03/30 00:35:52 rillig Exp $
#
# Tests for the variable modifier ':from=to', which replaces the suffix
# "from" with "to". It can also use '%' as a wildcard.
@@ -49,7 +49,7 @@
. error
.endif
-# In the modifier ':from=to', both parts can contain variable expressions.
+# In the modifier ':from=to', both parts can contain expressions.
.if ${one two:L:${:Uone}=${:U1}} != "1 two"
. error
.endif
@@ -69,7 +69,7 @@
.endif
# The replacement string can contain spaces, thereby changing the number
-# of words in the variable expression.
+# of words in the expression.
.if ${In:L:%=% ${:Uthe Sun}} != "In the Sun"
. error
.endif
@@ -206,12 +206,13 @@
. error
.endif
-# This is not a SysV modifier since the nested variable expression expands
+# This is not a SysV modifier since the nested expression expands
# to an empty string. The '=' in it should be irrelevant during parsing.
-# XXX: As of 2020-12-05, this expression generates an "Unfinished modifier"
+# XXX: As of 2024-06-30, this expression generates an "Unfinished modifier"
# error, while the correct error message would be "Unknown modifier" since
# there is no modifier named "fromto".
-.if ${word214:L:from${:D=}to}
+# expect+1: Unfinished modifier after "from${:D=}to}", expecting "="
+.if ${word216:L:from${:D=}to}
. error
.endif
@@ -220,7 +221,7 @@
# "fromto}...". The next modifier is a SysV modifier. ApplyModifier_SysV
# parses the modifier as "from${:D=}to", ending at the '}'. Next, the two
# parts of the modifier are parsed using ParseModifierPart, which scans
-# differently, properly handling nested variable expressions. The two parts
+# differently, properly handling nested expressions. The two parts
# are now "fromto}..." and "replaced".
.if "${:Ufromto\}...:from${:D=}to}...=replaced}" != "replaced"
. error
@@ -251,4 +252,35 @@ INDIRECT= 1:${VALUE} 2:$${VALUE} 4:$$$${VALUE}
. endfor
.endfor
+
+# The error case of an unfinished ':from=to' modifier after the '=' requires
+# an expression that is missing the closing '}'.
+# expect+1: Unfinished modifier after "$(})", expecting "}"
+.if ${error:L:from=$(})
+.endif
+
+
+# The various ":t..." modifiers fall back to the ":from=to" modifier.
+.if ${:Utarget:target=source} != "source"
+. error
+.endif
+.if ${:Ufile.ts:ts=js} != "file.js"
+. error
+.endif
+.if ${:Ufile.tsx:tsx=jsx} != "file.jsx"
+. error
+.endif
+.if ${:Ufile.ts\\part:ts\part=replaced} != "file.replaced"
+. error
+.endif
+.if ${:Ufile.ts\\123xyz:ts\123xyz=gone} != "file.gone"
+. error
+.endif
+# Since the ":ts=" modifier is a valid form of the ":ts" modifier, don't fall
+# back to the ":from=to" modifier.
+.if ${:U1 2 3 file.ts:ts=} != "1=2=3=file.ts"
+. error
+.endif
+
+
all:
diff --git a/contrib/bmake/unit-tests/varmod-tail.mk b/contrib/bmake/unit-tests/varmod-tail.mk
index 05eae481fe3e..614c1af2a0b4 100644
--- a/contrib/bmake/unit-tests/varmod-tail.mk
+++ b/contrib/bmake/unit-tests/varmod-tail.mk
@@ -1,8 +1,16 @@
-# $NetBSD: varmod-tail.mk,v 1.4 2020/12/20 22:57:40 rillig Exp $
+# $NetBSD: varmod-tail.mk,v 1.5 2024/06/01 18:44:05 rillig Exp $
#
# Tests for the :T variable modifier, which returns the basename of each of
# the words in the variable value.
+
+# If the ':T' is not directly followed by a delimiting ':' or '}', the
+# ':from=to' modifier is tried as a fallback.
+.if ${:U Tail :Tail=replaced} != "replaced"
+. error
+.endif
+
+
all:
.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a trailing/
@echo "tail (basename) of '"${path:Q}"' is '"${path:T:Q}"'"
diff --git a/contrib/bmake/unit-tests/varmod-to-abs.exp b/contrib/bmake/unit-tests/varmod-to-abs.exp
index 426b4d39744f..2865149a06ca 100644
--- a/contrib/bmake/unit-tests/varmod-to-abs.exp
+++ b/contrib/bmake/unit-tests/varmod-to-abs.exp
@@ -1,5 +1,5 @@
-make: "varmod-to-abs.mk" line 18: does-not-exist.c
-make: "varmod-to-abs.mk" line 19: does-not-exist.c
+make: varmod-to-abs.mk:19: does-not-exist.c
+make: varmod-to-abs.mk:21: does-not-exist.c
cached_realpath: varmod-to-abs.mk -> varmod-to-abs.mk
-make: "varmod-to-abs.mk" line 23: varmod-to-abs.mk
+make: varmod-to-abs.mk:26: varmod-to-abs.mk
exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-to-abs.mk b/contrib/bmake/unit-tests/varmod-to-abs.mk
index 7f23318487e3..bc4722068988 100644
--- a/contrib/bmake/unit-tests/varmod-to-abs.mk
+++ b/contrib/bmake/unit-tests/varmod-to-abs.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-to-abs.mk,v 1.5 2020/11/15 05:48:17 rillig Exp $
+# $NetBSD: varmod-to-abs.mk,v 1.6 2023/06/01 20:56:35 rillig Exp $
#
# Tests for the :tA variable modifier, which returns the absolute path for
# each of the words in the variable value.
@@ -15,11 +15,14 @@
# keeping the cache, just like the GNode for global variables.
.MAKEFLAGS: -dd
does-not-exist.c= /dev/null
+# expect+1: does-not-exist.c
.info ${does-not-exist.c:L:tA}
+# expect+1: does-not-exist.c
.info ${does-not-exist.c:L:tA}
# The output of the following line is modified by the global _SED_CMDS in
# unit-tests/Makefile. See the .rawout file for the truth.
+# expect+1: varmod-to-abs.mk
.info ${MAKEFILE:tA}
.MAKEFLAGS: -d0
diff --git a/contrib/bmake/unit-tests/varmod-to-lower.mk b/contrib/bmake/unit-tests/varmod-to-lower.mk
index 19d3406054b7..44116fd3eee2 100644
--- a/contrib/bmake/unit-tests/varmod-to-lower.mk
+++ b/contrib/bmake/unit-tests/varmod-to-lower.mk
@@ -1,7 +1,7 @@
-# $NetBSD: varmod-to-lower.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: varmod-to-lower.mk,v 1.6 2022/11/29 23:54:55 rillig Exp $
#
-# Tests for the :tl variable modifier, which returns the words in the
-# variable value, converted to lowercase.
+# Tests for the :tl variable modifier, which converts the expression value
+# to lowercase.
#
# TODO: What about non-ASCII characters? ISO-8859-1, UTF-8?
@@ -17,5 +17,10 @@
. error
.endif
-all:
- @:;
+# The ':tl' modifier works on the whole string, without splitting it into
+# words.
+.if ${:Umultiple spaces:tl} != "multiple spaces"
+. error
+.endif
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/varmod-to-separator.exp b/contrib/bmake/unit-tests/varmod-to-separator.exp
index bfcfa3ebc103..e8d5686c93c8 100644
--- a/contrib/bmake/unit-tests/varmod-to-separator.exp
+++ b/contrib/bmake/unit-tests/varmod-to-separator.exp
@@ -1,25 +1,29 @@
-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 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 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 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 196: Malformed conditional (${1 2 3:L:ts\x40g})
-make: Bad modifier ":tx" for variable "WORDS"
-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 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: varmod-to-separator.mk:154: Invalid character number at "400:tu}"
+ while evaluating variable "WORDS" with value "one two three"
+make: varmod-to-separator.mk:169: Invalid character number at "100:tu}"
+ while evaluating variable "WORDS" with value "one two three"
+make: varmod-to-separator.mk:177: Invalid character number at ",}"
+ while evaluating variable "word" with value "word"
+make: varmod-to-separator.mk:183: Invalid character number at "112233445566778899}"
+ while evaluating variable "word" with value "word"
+make: varmod-to-separator.mk:188: Unknown modifier ":ts\-300"
+ while evaluating variable "WORDS" with value "one two three"
+make: varmod-to-separator.mk:197: Unknown modifier ":ts\8"
+ while evaluating variable "1 2 3" with value "1 2 3"
+make: varmod-to-separator.mk:205: Unknown modifier ":ts\100L"
+ while evaluating variable "1 2 3" with value "1 2 3"
+make: varmod-to-separator.mk:213: Unknown modifier ":ts\x40g"
+ while evaluating variable "1 2 3" with value "1 2 3"
+make: varmod-to-separator.mk:222: Unknown modifier ":tx"
+ while evaluating variable "WORDS" with value "one two three four five six"
+make: varmod-to-separator.mk:230: Unknown modifier ":ts\X"
+ while evaluating variable "WORDS" with value "one two three four five six"
+make: varmod-to-separator.mk:239: Unknown modifier ":ts\X"
+ while evaluating variable "WORDS" with value "one two three four five six"
+make: varmod-to-separator.mk:255: Unknown modifier ":ts\69"
+ while evaluating "${:Ua b:ts\69}" with value "a b"
+make: varmod-to-separator.mk:263: Invalid character number at "1F60E}"
+ while evaluating "${:Ua b:ts\x1F60E}" with value "a b"
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 bf960639f831..18986e7f4bea 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.11 2022/02/09 21:09:24 rillig Exp $
+# $NetBSD: varmod-to-separator.mk,v 1.23 2025/03/30 00:35:52 rillig Exp $
#
# Tests for the :ts variable modifier, which joins the words of the variable
# using an arbitrary character as word separator.
@@ -7,30 +7,30 @@ WORDS= one two three four five six
# The words are separated by a single space, just as usual.
.if ${WORDS:ts } != "one two three four five six"
-. warning Space as separator does not work.
+. error
.endif
# The separator can be an arbitrary character, for example a comma.
.if ${WORDS:ts,} != "one,two,three,four,five,six"
-. warning Comma as separator does not work.
+. error
.endif
# After the :ts modifier, other modifiers can follow.
.if ${WORDS:ts/:tu} != "ONE/TWO/THREE/FOUR/FIVE/SIX"
-. warning Chaining modifiers does not work.
+. error
.endif
# To use the ':' as the separator, just write it normally.
# The first colon is the separator, the second ends the modifier.
.if ${WORDS:ts::tu} != "ONE:TWO:THREE:FOUR:FIVE:SIX"
-. warning Colon as separator does not work.
+. error
.endif
# When there is just a colon but no other character, the words are
# "separated" by an empty string, that is, they are all squashed
# together.
.if ${WORDS:ts:tu} != "ONETWOTHREEFOURFIVESIX"
-. warning Colon as separator does not work.
+. error
.endif
# Applying the :tu modifier first and then the :ts modifier does not change
@@ -39,45 +39,45 @@ WORDS= one two three four five six
# quote though, or other special characters like dollar or backslash.
#
# This example also demonstrates that the closing brace is not interpreted
-# as a separator, but as the closing delimiter of the whole variable
+# as a separator, but as the closing delimiter of the whole
# expression.
.if ${WORDS:tu:ts} != "ONETWOTHREEFOURFIVESIX"
-. warning Colon as separator does not work.
+. error
.endif
# The '}' plays the same role as the ':' in the preceding examples.
# Since there is a single character before it, that character is taken as
# the separator.
.if ${WORDS:tu:ts/} != "ONE/TWO/THREE/FOUR/FIVE/SIX"
-. warning Colon as separator does not work.
+. error
.endif
# Now it gets interesting and ambiguous: The separator could either be empty
# since it is followed by a colon. Or it could be the colon since that
# colon is followed by the closing brace. It's the latter case.
.if ${WORDS:ts:} != "one:two:three:four:five:six"
-. warning Colon followed by closing brace does not work.
+. error
.endif
# As in the ${WORDS:tu:ts} example above, the separator is empty.
.if ${WORDS:ts} != "onetwothreefourfivesix"
-. warning Empty separator before closing brace does not work.
+. error
.endif
# The :ts modifier can be followed by other modifiers.
.if ${WORDS:ts:S/two/2/} != "one2threefourfivesix"
-. warning Separator followed by :S modifier does not work.
+. error
.endif
# The :ts modifier can follow other modifiers.
.if ${WORDS:S/two/2/:ts} != "one2threefourfivesix"
-. warning :S modifier followed by :ts modifier does not work.
+. error
.endif
# The :ts modifier with an actual separator can be followed by other
# modifiers.
.if ${WORDS:ts/:S/two/2/} != "one/2/three/four/five/six"
-. warning The :ts modifier followed by an :S modifier does not work.
+. error
.endif
# After the modifier ':ts/', the expression value is a single word since all
@@ -128,80 +128,97 @@ WORDS= one two three four five six
# 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.
+. error
.endif
# The separator can be \t, which is a tab.
.if ${WORDS:[1..3]:ts\t} != "one two three"
-. warning The separator \t does not produce a tab.
+. error
.endif
# The separator can be given as octal number.
.if ${WORDS:[1..3]:ts\012:tu} != "ONE${.newline}TWO${.newline}THREE"
-. warning The separator \012 is not interpreted in octal ASCII.
+. error
.endif
# The octal number can have as many digits as it wants.
.if ${WORDS:[1..2]:ts\000000000000000000000000012:tu} != "ONE${.newline}TWO"
-. warning The separator \012 cannot have many leading zeroes.
+. error
.endif
# The value of the separator character must not be outside the value space
# for an unsigned character though.
#
# Since 2020-11-01, these out-of-bounds values are rejected.
+# expect+1: Invalid character number at "400:tu}"
.if ${WORDS:[1..3]:ts\400:tu}
-. warning The separator \400 is accepted even though it is out of bounds.
+. error
.else
-. warning The separator \400 is accepted even though it is out of bounds.
+. error
.endif
# The separator can be given as hexadecimal number.
.if ${WORDS:[1..3]:ts\xa:tu} != "ONE${.newline}TWO${.newline}THREE"
-. warning The separator \xa is not interpreted in hexadecimal ASCII.
+. error
.endif
# The hexadecimal number must be in the range of an unsigned char.
#
# Since 2020-11-01, these out-of-bounds values are rejected.
+# expect+1: Invalid character number at "100:tu}"
.if ${WORDS:[1..3]:ts\x100:tu}
-. warning The separator \x100 is accepted even though it is out of bounds.
+. error
.else
-. warning The separator \x100 is accepted even though it is out of bounds.
+. error
+.endif
+
+# The number after ':ts\x' must be hexadecimal.
+# expect+1: Invalid character number at ",}"
+.if ${word:L:ts\x,}
+.endif
+
+# The hexadecimal number must be in the range of 'unsigned long' on all
+# supported platforms.
+# expect+1: Invalid character number at "112233445566778899}"
+.if ${word:L:ts\x112233445566778899}
.endif
# Negative numbers are not allowed for the separator character.
+# expect+1: Unknown modifier ":ts\-300"
.if ${WORDS:[1..3]:ts\-300:tu}
-. warning The separator \-300 is accepted even though it is negative.
+. error
.else
-. warning The separator \-300 is accepted even though it is negative.
+. error
.endif
# The character number is interpreted as octal number by default.
# The digit '8' is not an octal digit though.
+# expect+1: Unknown modifier ":ts\8"
.if ${1 2 3:L:ts\8:tu}
-. warning The separator \8 is accepted even though it is not octal.
+. error
.else
-. warning The separator \8 is accepted even though it is not octal.
+. error
.endif
# Trailing characters after the octal character number are rejected.
+# expect+1: Unknown modifier ":ts\100L"
.if ${1 2 3:L:ts\100L}
-. warning The separator \100L is accepted even though it contains an 'L'.
+. error
.else
-. warning The separator \100L is accepted even though it contains an 'L'.
+. error
.endif
# Trailing characters after the hexadecimal character number are rejected.
+# expect+1: Unknown modifier ":ts\x40g"
.if ${1 2 3:L:ts\x40g}
-. warning The separator \x40g is accepted even though it contains a 'g'.
+. error
.else
-. warning The separator \x40g is accepted even though it contains a 'g'.
+. error
.endif
# In the :t modifier, the :t must be followed by any of A, l, s, u.
-# expect: make: Bad modifier ":tx" for variable "WORDS"
+# expect+1: Unknown modifier ":tx"
.if ${WORDS:tx}
. error
.else
@@ -209,7 +226,7 @@ WORDS= one two three four five six
.endif
# The word separator can only be a single character.
-# expect: make: Bad modifier ":ts\X" for variable "WORDS"
+# expect+1: Unknown modifier ":ts\X"
.if ${WORDS:ts\X}
. error
.else
@@ -218,8 +235,9 @@ WORDS= one two three four five six
# After the backslash, only n, t, an octal number, or x and a hexadecimal
# number are allowed.
-.if ${WORDS:t\X} != "anything"
-. info This line is not reached.
+# expect+1: Unknown modifier ":ts\X"
+.if ${WORDS:ts\X} != "anything"
+. error
.endif
@@ -233,7 +251,7 @@ WORDS= one two three four five six
# 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 ""
+# expect+1: Unknown modifier ":ts\69"
.if ${:Ua b:ts\69}
. error
.else
@@ -241,8 +259,7 @@ WORDS= one two three four five six
.endif
# Try whether bmake is Unicode-ready.
-# expect+2: Invalid character number at "1F60E}"
-# expect+1: Malformed conditional (${:Ua b:ts\x1F60E})
+# expect+1: Invalid character number at "1F60E}"
.if ${:Ua b:ts\x1F60E} # U+1F60E "smiling face with sunglasses"
. error
.else
diff --git a/contrib/bmake/unit-tests/varmod-to-title.exp b/contrib/bmake/unit-tests/varmod-to-title.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-to-title.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-to-title.mk b/contrib/bmake/unit-tests/varmod-to-title.mk
new file mode 100644
index 000000000000..f99e5441a8fb
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-to-title.mk
@@ -0,0 +1,31 @@
+# $NetBSD: varmod-to-title.mk,v 1.1 2024/07/01 21:02:26 sjg Exp $
+#
+# Tests for the :tc variable modifier, which converts the expression value
+# to lowercase.
+#
+# TODO: What about non-ASCII characters? ISO-8859-1, UTF-8?
+
+.if ${:UUPPER:tt} != "Upper"
+. error
+.endif
+
+.if ${:Ulower:tt} != "Lower"
+. error
+.endif
+
+.if ${:UMixeD case.:tt} != "Mixed Case."
+. error
+.endif
+
+# The ':tt' modifier works on the whole string, without splitting it into
+# words.
+.if ${:Umultiple spaces:tt} != "Multiple Spaces"
+. error
+.endif
+
+# Note words only count if separated by spaces
+.if ${:Uthis&that or os/2:tt} != "This&that Or Os/2"
+. error
+.endif
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/varmod-undefined.mk b/contrib/bmake/unit-tests/varmod-undefined.mk
index e06fc73244ab..4ab6db81351c 100644
--- a/contrib/bmake/unit-tests/varmod-undefined.mk
+++ b/contrib/bmake/unit-tests/varmod-undefined.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-undefined.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: varmod-undefined.mk,v 1.11 2024/06/03 02:46:29 sjg Exp $
#
# Tests for the :U variable modifier, which returns the given string
# if the variable is undefined.
@@ -7,6 +7,9 @@
# directive-for.mk
# varmod-defined.mk
+# this test depends on
+.MAKE.SAVE_DOLLARS= yes
+
# The pattern ${:Uword} is heavily used when expanding .for loops.
#
# This is how an expanded .for loop looks like.
@@ -19,17 +22,17 @@
.endif
# .endfor
-# The variable expressions in the text of the :U modifier may be arbitrarily
+# The expressions in the text of the :U modifier may be arbitrarily
# nested.
.if ${:U${:Unested}${${${:Udeeply}}}} != nested
. error
.endif
-# The nested variable expressions may contain braces, and these braces don't
+# The nested expressions may contain braces, and these braces don't
# need to match pairwise. In the following example, the :S modifier uses '{'
# as delimiter, which confuses both editors and humans because the opening
-# and # closing braces don't match anymore. It's syntactically valid though.
+# and closing braces don't match anymore. It's syntactically valid though.
# For more similar examples, see varmod-subst.mk, mod-subst-delimiter.
.if ${:U${:Uvalue:S{a{X{}} != vXlue
@@ -53,6 +56,10 @@
.if ${:U \: \} \$ \\ \a \b \n } != " : } \$ \\ \\a \\b \\n "
. error
.endif
+# An expression enclosed in quotes may be based on an undefined variable.
+.if "${:U \: \} \$ \\ \a \b \n }" != " : } \$ \\ \\a \\b \\n "
+. error
+.endif
# Even after the :U modifier has been applied, the expression still remembers
# that it originated from an undefined variable, and the :U modifier can
@@ -64,5 +71,43 @@
. error
.endif
-all:
- @:;
+
+# VARE_PARSE
+.if 0 && ${:U . \: \} \$ \\ ${EXPR}}
+. error
+.endif
+
+# VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED
+SUBST:= ${:U . \: \} \$ \\ ${EXPR}}
+${:U }= <space>
+EXPR= <expr>
+.if ${SUBST} != " . : } <space>\\ "
+. error
+.endif
+
+8_DOLLAR= $$$$$$$$
+.if ${8_DOLLAR} != "\$\$\$\$"
+. error
+.endif
+.if ${:U${8_DOLLAR}} != "\$\$\$\$"
+. error
+.endif
+.if ${x:L:@_@${8_DOLLAR}@} != "\$\$\$\$"
+. error
+.endif
+EXPR:= ${8_DOLLAR}
+.if ${EXPR} != "\$\$\$\$"
+. error
+.endif
+EXPR:= ${:U${8_DOLLAR}}
+.if ${EXPR} != "\$\$\$\$"
+. error
+.endif
+# VARE_EVAL_KEEP_UNDEFINED
+EXPR:= ${x:L:@_@${8_DOLLAR}@}
+.if ${EXPR} != "\$\$"
+. error
+.endif
+
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/varmod.exp b/contrib/bmake/unit-tests/varmod.exp
index e36c4ded9b47..022181d05cf1 100644
--- a/contrib/bmake/unit-tests/varmod.exp
+++ b/contrib/bmake/unit-tests/varmod.exp
@@ -1,8 +1,54 @@
-make: "varmod.mk" line 42: To escape a dollar, use \$, not $$, at "$$:L} != """
-make: "varmod.mk" line 42: Invalid variable name ':', at "$:L} != """
-make: "varmod.mk" line 47: Dollar followed by nothing
-make: "varmod.mk" line 56: Missing delimiter ':' after modifier "P"
-make: "varmod.mk" line 57: Missing argument for ".error"
+make: varmod.mk:111: To escape a dollar, use \$, not $$, at "$$:L} != """
+make: varmod.mk:111: Invalid variable name ":", at "$:L} != """
+make: varmod.mk:117: Dollar followed by nothing
+ while evaluating "${:Uword:@word@${word}$@} != "word"" with value "word"
+make: varmod.mk:125: Missing delimiter ":" after modifier "P"
+ while evaluating variable "VAR" with value "VAR"
+make: varmod.mk:134: Invalid modifier ":[99333000222000111000]"
+ while evaluating variable "word" with value "word"
+make: varmod.mk:137: Invalid modifier ":[2147483648]"
+ while evaluating variable "word" with value "word"
+make: varmod.mk:143: Invalid number "99333000222000111000}" for modifier ":range"
+ while evaluating variable "word" with value "word"
+make: varmod.mk:150: Invalid time value "\"
+ while evaluating indirect modifiers "gmtime=\"
+ while evaluating "${:${:Ugmtime=\\}}" with value ""
+make: varmod.mk:165: Dollar followed by nothing
+ while evaluating variable "VAR" with value "value$"
+make: varmod.mk:171: Dollar followed by nothing
+ while evaluating variable "VAR" with value "value$"
+make: varmod.mk:171: Dollar followed by nothing
+ while evaluating variable "VAR" with value "value$ appended$"
+make: varmod.mk:181: Dollar followed by nothing
+ while evaluating variable "word" with value "word"
+make: varmod.mk:185: Invalid modifier ":[$]"
+ while evaluating variable "word" with value ""
+make: varmod.mk:202: Dollar followed by nothing
+ while evaluating variable "VAR" with value "value$ appended$"
+make: varmod.mk:202: Invalid variable name "}", at "$} != "set""
+ while evaluating variable "VAR" with value "value<space>appended"
+make: varmod.mk:206: Invalid variable name "}", at "$} != "fallback""
+ while evaluating "${:Ufallback$} != "fallback"" with value ""
+make: varmod.mk:210: Invalid time value "1000$"
+ while evaluating variable "%y" with value "%y"
+make: varmod.mk:216: Invalid time value "1000$"
+ while evaluating variable "%y" with value "%y"
+make: varmod.mk:222: Dollar followed by nothing
+ while evaluating variable "word" with value "word"
+make: varmod.mk:226: Dollar followed by nothing
+ while evaluating variable "word" with value "word"
+make: varmod.mk:230: Invalid argument "fallback$" for modifier ":mtime"
+ while evaluating variable "." with value "."
+make: varmod.mk:244: Missing delimiter ":" after modifier "L"
+ while evaluating variable "VAR" with value "VAR"
+make: varmod.mk:256: Invalid time value " : } \ $ ) \) ( "
+ while evaluating variable "%Y" with value "%Y"
+make: varmod.mk:263: Invalid time value " : \) \ $ "
+ while evaluating variable "%Y" with value "%Y"
+make: varmod.mk:268: Invalid time value " : } \ $ ) \) ( "
+ while evaluating variable "%Y" with value "%Y"
+make: varmod.mk:273: Invalid time value " : \) \ $ "
+ while evaluating variable "%Y" with value "%Y"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varmod.mk b/contrib/bmake/unit-tests/varmod.mk
index 21ddf9103251..af976f9d0086 100644
--- a/contrib/bmake/unit-tests/varmod.mk
+++ b/contrib/bmake/unit-tests/varmod.mk
@@ -1,11 +1,78 @@
-# $NetBSD: varmod.mk,v 1.5 2020/12/19 22:33:11 rillig Exp $
+# $NetBSD: varmod.mk,v 1.30 2025/06/29 11:27:21 rillig Exp $
#
# Tests for variable modifiers, such as :Q, :S,from,to or :Ufallback.
+#
+# See also:
+# varparse-errors.mk
+
+# As of 2024-06-05, the possible behaviors during parsing are:
+#
+# * `strict`: the parsing style used by most modifiers:
+# * either uses `ParseModifierPart` or parses the modifier literal
+# * other modifiers may follow, separated by a ':'
+#
+# * `greedy`: calls `ParseModifierPart` with `ch->endc`; this means
+# that no further modifiers are parsed in that expression.
+#
+# * `no-colon`: after parsing this modifier, the following modifier
+# does not need to be separated by a colon.
+# Omitting this colon is bad style.
+#
+# * `individual`: parsing this modifier does not follow the common
+# pattern of calling `ParseModifierPart`.
+#
+# The SysV column says whether a modifier falls back trying the `:from=to`
+# System V modifier. Remarks:
+#
+# In the assignment modifiers `::=` and its variants, the `=` is part of
+# the modifier name, so they never fall back to the `:from=to` modifier.
+#
+# All no-colon modifiers get a "no", as the modifier name would be
+# trimmed off before the `:from=to` modifier could see them, for
+# example, ${VAR:LAR=ALUE} and ${VAR:L:AR=ALUE} behave the same.
+#
+# | **Modifier** | **Behavior** | **Remarks** | **SysV** |
+# |--------------|--------------|--------------------|----------|
+# | ! | no-colon | | no |
+# | := | greedy | | no |
+# | :?= | greedy | | no |
+# | :+= | greedy | | no |
+# | :!= | greedy | | no |
+# | ?: | greedy | | no |
+# | @ | no-colon | | no |
+# | C | no-colon | | no |
+# | D | individual | custom parser | no |
+# | E | strict | | yes |
+# | H | strict | | yes |
+# | L | no-colon | | no |
+# | M | individual | custom parser | no |
+# | N | individual | custom parser | no |
+# | O | strict | only literal value | yes |
+# | P | no-colon | | no |
+# | Q | strict | | yes |
+# | R | strict | | yes |
+# | S | no-colon | | no |
+# | T | strict | | yes |
+# | U | individual | custom parser | no |
+# | [ | strict | | no |
+# | _ | individual | strcspn | no |
+# | gmtime | strict | | no |
+# | hash | strict | | yes |
+# | localtime | strict | | no |
+# | q | strict | | yes |
+# | range | strict | | no |
+# | sh | strict | | yes |
+# | t | strict | | yes |
+# | u | strict | | yes |
+# | from=to | greedy | SysV, fallback | --- |
+
+# These tests assume
+.MAKE.SAVE_DOLLARS = yes
DOLLAR1= $$
DOLLAR2= ${:U\$}
-# To get a single '$' sign in the value of a variable expression, it has to
+# To get a single '$' sign in the value of an expression, it has to
# be written as '$$' in a literal variable value.
#
# See Var_Parse, where it calls Var_Subst.
@@ -39,22 +106,170 @@ DOLLAR2= ${:U\$}
# For compatibility, make does not print these error messages in normal mode.
# Should it?
.MAKEFLAGS: -dL
+# expect+2: To escape a dollar, use \$, not $$, at "$$:L} != """
+# expect+1: Invalid variable name ":", at "$:L} != """
.if ${$$:L} != ""
. error
.endif
# A '$' followed by nothing is an error as well.
+# expect+1: Dollar followed by nothing
.if ${:Uword:@word@${word}$@} != "word"
. error
.endif
-# The variable modifier :P does not fall back to the SysV modifier.
+# The modifier :P does not fall back to the SysV modifier.
# Therefore the modifier :P=RE generates a parse error.
-# XXX: The .error should not be reached since the variable expression is
-# malformed, and this error should be propagated up to Cond_EvalLine.
VAR= STOP
+# expect+1: Missing delimiter ":" after modifier "P"
.if ${VAR:P=RE} != "STORE"
. error
+.else
+. error
+.endif
+
+# Test the word selection modifier ':[n]' with a very large number that is
+# larger than ULONG_MAX for any supported platform.
+# expect+1: Invalid modifier ":[99333000222000111000]"
+.if ${word:L:[99333000222000111000]}
+.endif
+# expect+1: Invalid modifier ":[2147483648]"
+.if ${word:L:[2147483648]}
+.endif
+
+# Test the range generation modifier ':range=n' with a very large number that
+# is larger than SIZE_MAX for any supported platform.
+# expect+1: Invalid number "99333000222000111000}" for modifier ":range"
+.if ${word:L:range=99333000222000111000}
+.endif
+
+# In an indirect modifier, the delimiter is '\0', which at the same time marks
+# the end of the string. The sequence '\\' '\0' is not an escaped delimiter,
+# as it would be wrong to skip past the end of the string.
+# expect+1: Invalid time value "\"
+.if ${:${:Ugmtime=\\}}
+. error
+.endif
+
+# Test a '$' at the end of a modifier part, for all modifiers in the order
+# listed in ApplyModifier.
+#
+# The only modifier parts where an unescaped '$' makes sense at the end are
+# the 'from' parts of the ':S' and ':C' modifiers. In all other modifier
+# parts, an unescaped '$' is an undocumented and discouraged edge case, as it
+# means the same as an escaped '$'.
+.if ${:U:!printf '%s\n' $!} != "\$"
+. error
+.endif
+# expect+1: Dollar followed by nothing
+.if ${VAR::=value$} != "" || ${VAR} != "value"
+. error
+.endif
+${:U }= <space>
+# expect+2: Dollar followed by nothing
+# expect+1: Dollar followed by nothing
+.if ${VAR::+=appended$} != "" || ${VAR} != "value<space>appended"
+. error
+.endif
+.if ${1:?then$:else$} != "then\$"
+. error
+.endif
+.if ${0:?then$:else$} != "else\$"
+. error
+.endif
+# expect+1: Dollar followed by nothing
+.if ${word:L:@w@$w$@} != "word"
+. error
+.endif
+# expect+1: Invalid modifier ":[$]"
+.if ${word:[$]}
+. error
+.else
+. error
+.endif
+VAR_DOLLAR= VAR$$
+.if ${word:L:_=VAR$} != "word" || ${${VAR_DOLLAR}} != "word"
+. error
+.endif
+.if ${word:L:C,d$,m,} != "worm"
+. error
+.endif
+.if ${word:L:C,d,$,} != "wor\$"
+. error
+.endif
+# expect+2: Dollar followed by nothing
+# expect+1: Invalid variable name "}", at "$} != "set""
+.if ${VAR:Dset$} != "set"
+. error
+.endif
+# expect+1: Invalid variable name "}", at "$} != "fallback""
+.if ${:Ufallback$} != "fallback"
+. error
+.endif
+# expect+1: Invalid time value "1000$"
+.if ${%y:L:gmtime=1000$}
+. error
+.else
+. error
+.endif
+# expect+1: Invalid time value "1000$"
+.if ${%y:L:localtime=1000$}
+. error
+.else
+. error
+.endif
+# expect+1: Dollar followed by nothing
+.if ${word:L:Mw*$} != "word"
+. error
+.endif
+# expect+1: Dollar followed by nothing
+.if ${word:L:NX*$} != "word"
+. error
+.endif
+# expect+1: Invalid argument "fallback$" for modifier ":mtime"
+.if ${.:L:mtime=fallback$}
+. error
+.else
+. error
+.endif
+.if ${word:L:S,d$,m,} != "worm"
+. error
+.endif
+.if ${word:L:S,d,m$,} != "worm\$"
+. error
+.endif
+
+.undef VAR
+# expect+1: Missing delimiter ":" after modifier "L"
+.if ${VAR:LAR=ALUE} != "VALUE"
+. error
+.endif
+.if ${VAR:L:AR=ALUE} != "VALUE"
+. error
.endif
-all: # nothing
+
+# When an expression has the usual form ${...} with braces,
+# in the part of a modifier, ":}\$" can be escaped using a backslash.
+# All other characters are passed through unmodified.
+# expect+1: Invalid time value " : } \ $ ) \) ( "
+.if ${%Y:L:localtime= \: \} \\ \$ ) \) ( :M*} != ": } \\ \$ ) \\) ("
+. error
+.endif
+# When an expression has the unusual form $(...) with parentheses,
+# in the part of a modifier, ":)\$" can be escaped using a backslash.
+# All other characters are passed through unmodified.
+# expect+1: Invalid time value " : \) \ $ "
+.if ${%Y:L:localtime= \: \) \\ \$ } \} { :M*} != ": ) \\ \$ } \\} {"
+. error
+.endif
+# Same when the modifier is the last modifier in an expression.
+# expect+1: Invalid time value " : } \ $ ) \) ( "
+.if ${%Y:L:localtime= \: \} \\ \$ ) \) ( } != " : } \\ \$ ) \\) ( "
+. error
+.endif
+# Same when the modifier is the last modifier in an expression.
+# expect+1: Invalid time value " : \) \ $ "
+.if ${%Y:L:localtime= \: \) \\ \$ } \} { } != " : ) \\ \$ } \\} { "
+. error
+.endif
diff --git a/contrib/bmake/unit-tests/varname-circumflex.exp b/contrib/bmake/unit-tests/varname-circumflex.exp
new file mode 100644
index 000000000000..184b021fd5e5
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-circumflex.exp
@@ -0,0 +1,9 @@
+no_prerequisites:
+prerequisite: file1.o
+unique: file1.o file2.o file3.o
+duplicate: file1.o file2.o file3.o
+dir_part: /usr/include /usr/include .
+file_part: stdio.h unistd.h foo.h
+implicit.tout: implicit.tin
+wait: file1.o .WAIT_1 file2.o
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-circumflex.mk b/contrib/bmake/unit-tests/varname-circumflex.mk
new file mode 100644
index 000000000000..270f7123781b
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-circumflex.mk
@@ -0,0 +1,47 @@
+# $NetBSD: varname-circumflex.mk,v 1.1 2025/06/27 20:20:56 rillig Exp $
+#
+# Tests for the target-local variable "^", which is required by POSIX 2024
+# and provided by GNU make.
+
+# TODO: Support $^.
+
+all: .PHONY
+all: no_prerequisites prerequisite
+all: unique duplicate
+all: dir_part file_part
+all: implicit.tout
+all: wait
+
+.if defined(^)
+. error
+.endif
+
+no_prerequisites:
+ @echo $@: $^
+
+prerequisite: file1.o
+ @echo $@: $^
+
+unique: file1.o file2.o file3.o
+ @echo $@: $^
+
+duplicate: file1.o file2.o file3.o file3.o
+ @echo $@: $^
+
+dir_part: /usr/include/stdio.h /usr/include/unistd.h foo.h
+ @echo $@: $(^D)
+
+file_part: /usr/include/stdio.h /usr/include/unistd.h foo.h
+ @echo $@: ${^F}
+
+wait: file1.o .WAIT file2.o
+ @echo $@: $^
+
+.SUFFIXES:
+.SUFFIXES: .tin .tout
+
+.tin.tout:
+ @echo $@: $^
+
+file1.o file2.o file3.o:
+/usr/include/stdio.h /usr/include/unistd.h foo.h implicit.tin:
diff --git a/contrib/bmake/unit-tests/varname-dollar.exp b/contrib/bmake/unit-tests/varname-dollar.exp
index c880e82f0170..08832792e35c 100644
--- a/contrib/bmake/unit-tests/varname-dollar.exp
+++ b/contrib/bmake/unit-tests/varname-dollar.exp
@@ -1,5 +1,5 @@
-make: "varname-dollar.mk" line 16: dollar is $.
-make: "varname-dollar.mk" line 17: dollar in braces is .
-make: "varname-dollar.mk" line 25: dollar is $.
-make: "varname-dollar.mk" line 26: dollar in braces is dollar.
+make: varname-dollar.mk:17: dollar is $.
+make: varname-dollar.mk:19: dollar in braces is .
+make: varname-dollar.mk:28: dollar is $.
+make: varname-dollar.mk:30: dollar in braces is dollar.
exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dollar.mk b/contrib/bmake/unit-tests/varname-dollar.mk
index d1db9f833306..e60c9dd43e8f 100644
--- a/contrib/bmake/unit-tests/varname-dollar.mk
+++ b/contrib/bmake/unit-tests/varname-dollar.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varname-dollar.mk,v 1.3 2020/08/19 05:40:06 rillig Exp $
+# $NetBSD: varname-dollar.mk,v 1.4 2023/06/01 20:56:35 rillig Exp $
#
# Tests for the expression "$$", which looks as if it referred to a variable,
# but simply expands to a single '$' sign.
@@ -13,7 +13,9 @@ DOLLAR= $$
# At this point, the variable '$' is not defined. Therefore the second line
# returns an empty string.
+# expect+1: dollar is $.
.info dollar is $$.
+# expect+1: dollar in braces is .
.info dollar in braces is ${${DOLLAR}}.
# Now overwrite the '$' variable to see whether '$$' really expands to that
@@ -22,7 +24,9 @@ ${DOLLAR}= dollar
# At this point, the variable '$' is defined, therefore its value is printed
# in the second .info directive.
+# expect+1: dollar is $.
.info dollar is $$.
+# expect+1: dollar in braces is dollar.
.info dollar in braces is ${${DOLLAR}}.
all:
diff --git a/contrib/bmake/unit-tests/varname-dot-make-jobs.exp b/contrib/bmake/unit-tests/varname-dot-make-jobs.exp
index 1308f9116240..a899c5d38418 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-jobs.exp
+++ b/contrib/bmake/unit-tests/varname-dot-make-jobs.exp
@@ -4,5 +4,5 @@ undefined
5
--- echo ---
20
-00000000000000000000000000000001
+1
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 af5eebfe7498..5ed62c180eed 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-jobs.mk
+++ b/contrib/bmake/unit-tests/varname-dot-make-jobs.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varname-dot-make-jobs.mk,v 1.3 2022/01/26 22:47:03 rillig Exp $
+# $NetBSD: varname-dot-make-jobs.mk,v 1.5 2023/09/10 16:25:32 sjg Exp $
#
# 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.
@@ -15,10 +15,29 @@ all:
@${MAKE} -r -f ${MAKEFILE} echo -j20
@${MAKE} -r -f ${MAKEFILE} echo -j00000000000000000000000000000001
+.if !make(echo) && ${.MAKE.JOBS.C} == "yes"
+# These results will not be static, we need NCPU
+# to compute expected results.
+all: jC
+
+NCPU!= ${MAKE} -r -f /dev/null -jC -V .MAKE.JOBS
+
+# If -j arg is floating point or ends in C;
+# .MAKE.JOBS is a multiple of _SC_NPROCESSORS_ONLN
+# No news is good news here.
+jCvals ?= 1 1.2 2
+
+jC:
+ @for j in ${jCvals}; do \
+ e=`echo "${NCPU} * $$j" | bc | sed 's/\.[0-9]*//'`; \
+ g=`${MAKE} -r -f /dev/null -V .MAKE.JOBS -j$${j}C`; \
+ test $$g = $$e || echo "$$g != $$e"; \
+ done
+
+.endif
+
# 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
+# expect: 1
diff --git a/contrib/bmake/unit-tests/varname-dot-make-level.exp b/contrib/bmake/unit-tests/varname-dot-make-level.exp
index 39a9383953dd..1209cf3b4af8 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-level.exp
+++ b/contrib/bmake/unit-tests/varname-dot-make-level.exp
@@ -1 +1,12 @@
+level 1: variable 0, env 1
+level 2: variable 1, env 2
+level 3: variable 2, env 3
+: set-env-same
+ok
+: set-env-different
+make: Cannot override read-only global variable ".MAKE.LEVEL.ENV" with a command line variable
+ in make[1] in directory "<curdir>"
+make: Fatal errors encountered -- cannot continue
+make: stopped making "ok" in unit-tests
+set-env-different: exit 1
exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-level.mk b/contrib/bmake/unit-tests/varname-dot-make-level.mk
index c4f2c0db7da6..a85fbeb53b4c 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-level.mk
+++ b/contrib/bmake/unit-tests/varname-dot-make-level.mk
@@ -1,8 +1,55 @@
-# $NetBSD: varname-dot-make-level.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varname-dot-make-level.mk,v 1.6 2025/03/22 12:23:00 rillig Exp $
#
-# Tests for the special .MAKE.LEVEL variable.
+# Tests for the special .MAKE.LEVEL variable, which informs about the
+# recursion level. It is related to the environment variable MAKELEVEL,
+# even though they don't have the same value.
-# TODO: Implementation
+all: .PHONY level_1 set-env-same set-env-different
-all:
- @:;
+# expect: level 1: variable 0, env 1
+level_1: .PHONY
+ @printf 'level 1: variable %s, env %s\n' ${.MAKE.LEVEL} "$$${.MAKE.LEVEL.ENV}"
+ @${MAKE} -f ${MAKEFILE} level_2
+
+# expect: level 2: variable 1, env 2
+level_2: .PHONY
+ @printf 'level 2: variable %s, env %s\n' ${.MAKE.LEVEL} "$$${.MAKE.LEVEL.ENV}"
+ @${MAKE} -f ${MAKEFILE} level_3
+
+# The .unexport-env directive clears the environment, except for the
+# .MAKE.LEVEL.ENV make variable, which by default refers to the MAKELEVEL
+# environment variable.
+.if make(level_2)
+.unexport-env
+.endif
+
+# expect: level 3: variable 2, env 3
+level_3: .PHONY
+ @printf 'level 3: variable %s, env %s\n' ${.MAKE.LEVEL} "$$${.MAKE.LEVEL.ENV}"
+
+
+# When a variable assignment from the command line tries to override a
+# read-only global variable with the same value as before, ignore the
+# assignment, as the variable value would not change.
+#
+# This special case allows older versions of make to coexist with newer
+# versions of make. Older version of make (up to NetBSD 9) stored the internal
+# .MAKE.LEVEL.ENV variable in the scope for command line variables, and these
+# variables were passed to sub-makes via .MAKEOVERRIDES and the MAKEFLAGS
+# environment variable. Newer versions of make (since NetBSD 11) store the
+# internal .MAKE.LEVEL.ENV variable in the global scope but make it read-only
+# and prevent any attempts to override it.
+#
+# https://gnats.netbsd.org/59184
+set-env-same: .PHONY
+ : ${.TARGET}
+ @${MAKE} -f ${MAKEFILE} ok .MAKE.LEVEL.ENV=${.MAKE.LEVEL.ENV} || echo "${.TARGET}: exit $$?"
+
+
+# expect: make: Cannot override read-only global variable ".MAKE.LEVEL.ENV" with a command line variable
+set-env-different: .PHONY
+ : ${.TARGET}
+ @${MAKE} -f ${MAKEFILE} ok .MAKE.LEVEL.ENV=CUSTOM || echo "${.TARGET}: exit $$?"
+
+ok: .PHONY
+ @echo ok
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.exp b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.exp
index 39a9383953dd..0422b06d5d63 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.exp
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.exp
@@ -1 +1,11 @@
+Initialize check-ignore.meta
+Building <tmpdir>/obj/check-ignore
+Skipping meta for .END: .SPECIAL
+Use .MAKE.META.IGNORE_FILTER - check-ignore is up to date
+`check-ignore' is up to date.
+Skipping meta for .END: .SPECIAL
+Skip .MAKE.META.IGNORE_FILTER - check-ignore is out of date
+<tmpdir>/obj/check-ignore.meta:<line>: file '<tmpdir>/ignore/check' is newer than the target...
+Building <tmpdir>/obj/check-ignore
+Skipping meta for .END: .SPECIAL
exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.mk b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.mk
index c41aec4acdf8..0adf6a202857 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.mk
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_filter.mk
@@ -1,8 +1,5 @@
-# $NetBSD: varname-dot-make-meta-ignore_filter.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varname-dot-make-meta-ignore_filter.mk,v 1.3 2023/02/23 05:20:45 sjg Exp $
#
# Tests for the special .MAKE.META.IGNORE_FILTER variable.
-# TODO: Implementation
-
-all:
- @:;
+.include "meta-ignore.inc"
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.exp b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.exp
index 39a9383953dd..7f2d61e4feb1 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.exp
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.exp
@@ -1 +1,11 @@
+Initialize check-ignore.meta
+Building <tmpdir>/obj/check-ignore
+Skipping meta for .END: .SPECIAL
+Use .MAKE.META.IGNORE_PATHS - check-ignore is up to date
+`check-ignore' is up to date.
+Skipping meta for .END: .SPECIAL
+Skip .MAKE.META.IGNORE_PATHS - check-ignore is out of date
+<tmpdir>/obj/check-ignore.meta:<line>: file '<tmpdir>/ignore/check' is newer than the target...
+Building <tmpdir>/obj/check-ignore
+Skipping meta for .END: .SPECIAL
exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.mk b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.mk
index 4ae34f51608b..2c58849af0be 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.mk
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_paths.mk
@@ -1,8 +1,5 @@
-# $NetBSD: varname-dot-make-meta-ignore_paths.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varname-dot-make-meta-ignore_paths.mk,v 1.3 2023/02/23 05:20:45 sjg Exp $
#
# Tests for the special .MAKE.META.IGNORE_PATHS variable.
-# TODO: Implementation
-
-all:
- @:;
+.include "meta-ignore.inc"
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.exp b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.exp
index 39a9383953dd..2b417e5b9e5a 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.exp
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.exp
@@ -1 +1,11 @@
+Initialize check-ignore.meta
+Building <tmpdir>/obj/check-ignore
+Skipping meta for .END: .SPECIAL
+Use .MAKE.META.IGNORE_PATTERNS - check-ignore is up to date
+`check-ignore' is up to date.
+Skipping meta for .END: .SPECIAL
+Skip .MAKE.META.IGNORE_PATTERNS - check-ignore is out of date
+<tmpdir>/obj/check-ignore.meta:<line>: file '<tmpdir>/ignore/check' is newer than the target...
+Building <tmpdir>/obj/check-ignore
+Skipping meta for .END: .SPECIAL
exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.mk b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.mk
index ea9fc49f1718..d3d6e065857d 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.mk
+++ b/contrib/bmake/unit-tests/varname-dot-make-meta-ignore_patterns.mk
@@ -1,8 +1,5 @@
-# $NetBSD: varname-dot-make-meta-ignore_patterns.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varname-dot-make-meta-ignore_patterns.mk,v 1.3 2023/02/23 05:20:45 sjg Exp $
#
# Tests for the special .MAKE.META.IGNORE_PATTERNS variable.
-# TODO: Implementation
-
-all:
- @:;
+.include "meta-ignore.inc"
diff --git a/contrib/bmake/unit-tests/varname-dot-make-mode.exp b/contrib/bmake/unit-tests/varname-dot-make-mode.exp
index 39a9383953dd..fa033e718b0c 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-mode.exp
+++ b/contrib/bmake/unit-tests/varname-dot-make-mode.exp
@@ -1 +1,31 @@
+randomize compat mode:
+: Making a
+: Making a
+: Making a
+: Making b
+: Making b
+: Making b
+: Making c
+: Making c
+: Making c
+randomize jobs mode (-j1):
+: Making a
+: Making a
+: Making a
+: Making b
+: Making b
+: Making b
+: Making c
+: Making c
+: Making c
+randomize jobs mode (-j5):
+: Making a
+: Making a
+: Making a
+: Making b
+: Making b
+: Making b
+: Making c
+: Making c
+: Making c
exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-mode.mk b/contrib/bmake/unit-tests/varname-dot-make-mode.mk
index ee75a54ebd74..169aa17cdc36 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-mode.mk
+++ b/contrib/bmake/unit-tests/varname-dot-make-mode.mk
@@ -1,8 +1,41 @@
-# $NetBSD: varname-dot-make-mode.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varname-dot-make-mode.mk,v 1.3 2022/05/07 17:49:47 rillig Exp $
#
# Tests for the special .MAKE.MODE variable.
-# TODO: Implementation
+# TODO: test .MAKE.MODE "meta", or see meta mode tests.
+# TODO: test .MAKE.MODE "compat"
-all:
- @:;
+
+# See Makefile, POSTPROC for the postprocessing that takes place.
+# See the .rawout file for the raw output before stripping the digits.
+all: .PHONY make-mode-randomize-targets
+
+
+# By adding the word "randomize-targets" to the variable .MAKE.MODE, the
+# targets are not made in declaration order, but rather in random order. This
+# mode helps to find undeclared dependencies between files.
+#
+# History
+# Added on 2022-05-07.
+#
+# See also
+# https://gnats.netbsd.org/45226
+make-mode-randomize-targets: .PHONY
+ @echo "randomize compat mode:"
+ @${MAKE} -r -f ${MAKEFILE} randomize-targets
+
+ @echo "randomize jobs mode (-j1):"
+ @${MAKE} -r -f ${MAKEFILE} -j1 randomize-targets
+
+ @echo "randomize jobs mode (-j5):"
+ @${MAKE} -r -f ${MAKEFILE} -j5 randomize-targets | grep '^:'
+
+.if make(randomize-targets)
+randomize-targets: .WAIT a1 a2 a3 .WAIT b1 b2 b3 .WAIT c1 c2 c3 .WAIT
+a1 a2 a3 b1 b2 b3 c1 c2 c3:
+ : Making ${.TARGET}
+
+# .MAKE.MODE is evaluated after parsing all files, so it suffices to switch
+# the mode after defining the targets.
+.MAKE.MODE+= randomize-targets
+.endif
diff --git a/contrib/bmake/unit-tests/varname-dot-makeflags.exp b/contrib/bmake/unit-tests/varname-dot-makeflags.exp
index dbf96469f86b..c5d5b120324c 100644
--- a/contrib/bmake/unit-tests/varname-dot-makeflags.exp
+++ b/contrib/bmake/unit-tests/varname-dot-makeflags.exp
@@ -1,3 +1,10 @@
-echo "$MAKEFLAGS"
- -r -k -d 00000 -D VARNAME WITH SPACES
+make: varname-dot-makeflags.mk:11: MAKEFLAGS=<undefined>
+make: varname-dot-makeflags.mk:13: .MAKEFLAGS=< -r -k>
+make: varname-dot-makeflags.mk:15: .MAKEOVERRIDES=<>
+make: varname-dot-makeflags.mk:21: MAKEFLAGS=<undefined>
+make: varname-dot-makeflags.mk:23: .MAKEFLAGS=< -r -k -D VARNAME -r>
+make: varname-dot-makeflags.mk:25: .MAKEOVERRIDES=< VAR>
+runtime: MAKEFLAGS=< -r -k -D VARNAME -r VAR=value>
+runtime: .MAKEFLAGS=< -r -k -D VARNAME -r>
+runtime: .MAKEOVERRIDES=< VAR>
exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-makeflags.mk b/contrib/bmake/unit-tests/varname-dot-makeflags.mk
index 10d1903022cb..87e4e4443cd2 100644
--- a/contrib/bmake/unit-tests/varname-dot-makeflags.mk
+++ b/contrib/bmake/unit-tests/varname-dot-makeflags.mk
@@ -1,15 +1,39 @@
-# $NetBSD: varname-dot-makeflags.mk,v 1.1 2020/12/01 20:37:30 rillig Exp $
+# $NetBSD: varname-dot-makeflags.mk,v 1.11 2025/05/20 17:56:40 sjg Exp $
#
# Tests for the special .MAKEFLAGS variable, which collects almost all
# command line arguments and passes them on to any child processes via
# the environment variable MAKEFLAGS (without leading '.').
+#
+# See also:
+# varname-dot-makeoverrides.mk
+
+# expect+1: MAKEFLAGS=<undefined>
+.info MAKEFLAGS=<${MAKEFLAGS:Uundefined}>
+# expect+1: .MAKEFLAGS=< -r -k>
+.info .MAKEFLAGS=<${.MAKEFLAGS}>
+# expect+1: .MAKEOVERRIDES=<>
+.info .MAKEOVERRIDES=<${.MAKEOVERRIDES:Uundefined}>
+
+# Append an option with argument, a plain option and a variable assignment.
+.MAKEFLAGS: -DVARNAME -r VAR=value
+
+# expect+1: MAKEFLAGS=<undefined>
+.info MAKEFLAGS=<${MAKEFLAGS:Uundefined}>
+# expect+1: .MAKEFLAGS=< -r -k -D VARNAME -r>
+.info .MAKEFLAGS=<${.MAKEFLAGS}>
+# expect+1: .MAKEOVERRIDES=< VAR>
+.info .MAKEOVERRIDES=<${.MAKEOVERRIDES}>
-# When options are parsed, the option and its argument are appended as
-# separate words to .MAKEFLAGS. Special characters in the option argument
-# are not quoted though. It seems to have not been necessary at least from
-# 1993 until 2020.
-.MAKEFLAGS: -d00000 -D"VARNAME WITH SPACES"
+# The environment variable 'MAKEFLAGS' is not available to child processes
+# when parsing the makefiles. This is different from exported variables,
+# which are already available during parse time.
+.if ${:!echo "\${MAKEFLAGS-undef}"!} != "undef"
+. error
+.endif
-all:
- echo "$$MAKEFLAGS"
- @:;
+# After parsing, the environment variable 'MAKEFLAGS' is set based on the
+# special variables '.MAKEFLAGS' and '.MAKEOVERRIDES'.
+runtime:
+ @echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
+ @echo '$@: .MAKEFLAGS=<'${.MAKEFLAGS:Q}'>'
+ @echo '$@: .MAKEOVERRIDES=<'${.MAKEOVERRIDES:Q}'>'
diff --git a/contrib/bmake/unit-tests/varname-dot-makeoverrides.exp b/contrib/bmake/unit-tests/varname-dot-makeoverrides.exp
index 39a9383953dd..21942ffdc96d 100644
--- a/contrib/bmake/unit-tests/varname-dot-makeoverrides.exp
+++ b/contrib/bmake/unit-tests/varname-dot-makeoverrides.exp
@@ -1 +1,8 @@
+all: overrides=<>
+make -f varname-dot-makeoverrides.mk stage_1 VAR=value
+stage_1: overrides=< VAR>
+make -f varname-dot-makeoverrides.mk stage_2 .VAR=too
+stage_2: overrides=< VAR .VAR>
+make -f varname-dot-makeoverrides.mk stage_3
+stage_3: overrides=< .VAR VAR>
exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-makeoverrides.mk b/contrib/bmake/unit-tests/varname-dot-makeoverrides.mk
index a897f4667175..966fcb7e0ec9 100644
--- a/contrib/bmake/unit-tests/varname-dot-makeoverrides.mk
+++ b/contrib/bmake/unit-tests/varname-dot-makeoverrides.mk
@@ -1,8 +1,23 @@
-# $NetBSD: varname-dot-makeoverrides.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varname-dot-makeoverrides.mk,v 1.6 2024/05/07 18:26:22 sjg Exp $
#
-# Tests for the special .MAKE.MAKEOVERRIDES variable.
-
-# TODO: Implementation
+# Tests for the special .MAKEOVERRIDES variable, which lists the names of the
+# variables that are passed on to child processes via the MAKEFLAGS
+# environment variable.
+#
+# See also:
+# varname-dot-makeflags.mk
all:
- @:;
+ @echo '$@: overrides=<'${.MAKEOVERRIDES:Uundefined:Q}'>'
+ ${MAKE} -f ${MAKEFILE} stage_1 VAR=value
+
+stage_1:
+ @echo '$@: overrides=<'${.MAKEOVERRIDES:Q}'>'
+ ${MAKE} -f ${MAKEFILE} stage_2 .VAR=too
+
+stage_2:
+ @echo '$@: overrides=<'${.MAKEOVERRIDES:Q}'>'
+ ${MAKE} -f ${MAKEFILE} stage_3
+
+stage_3:
+ @echo '$@: overrides=<'${.MAKEOVERRIDES:Q}'>'
diff --git a/contrib/bmake/unit-tests/varname-dot-newline.exp b/contrib/bmake/unit-tests/varname-dot-newline.exp
index 13943539bf3b..12114c9f24fd 100644
--- a/contrib/bmake/unit-tests/varname-dot-newline.exp
+++ b/contrib/bmake/unit-tests/varname-dot-newline.exp
@@ -1,4 +1,13 @@
-make: "varname-dot-newline.mk" line 16: The .newline variable can be overwritten. Just don't do that.
+make: varname-dot-newline.mk:28: Cannot overwrite ".newline" as it is read-only
+ in make[1] in directory "<curdir>"
+make: varname-dot-newline.mk:30: Cannot append to ".newline" as it is read-only
+ in make[1] in directory "<curdir>"
+make: varname-dot-newline.mk:32: Cannot delete ".newline" as it is read-only
+ in make[1] in directory "<curdir>"
+make: Fatal errors encountered -- cannot continue
+make: stopped making "try-to-modify" in unit-tests
first
second
+backslash newline: <\
+>
exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-newline.mk b/contrib/bmake/unit-tests/varname-dot-newline.mk
index 0565d244f298..69ab5af4cd6c 100644
--- a/contrib/bmake/unit-tests/varname-dot-newline.mk
+++ b/contrib/bmake/unit-tests/varname-dot-newline.mk
@@ -1,23 +1,42 @@
-# $NetBSD: varname-dot-newline.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: varname-dot-newline.mk,v 1.7 2024/06/15 22:06:31 rillig Exp $
#
-# Tests for the special .newline variable.
+# Tests for the special .newline variable, which contains a single newline
+# character (U+000A).
+
+
+# https://austingroupbugs.net/view.php?id=1549 proposes:
+# > After all macro expansion is complete, when an escaped <newline> is
+# > found in a command line in a makefile, the command line that is executed
+# > shall contain the <backslash>, the <newline>, and the next line, except
+# > that the first character of the next line shall not be included if it is
+# > a <tab>.
#
-# Contrary to the special variable named "" that is used in expressions like
-# ${:Usome-value}, the variable ".newline" is not protected against
-# modification. Nobody exploits that though.
+# The above quote assumes that each resulting <newline> character has a "next
+# line", but that's not how the .newline variable works.
+BACKSLASH_NEWLINE:= \${.newline}
+
+
+# Check that .newline is read-only
NEWLINE:= ${.newline}
+.if make(try-to-modify)
+# A '?=' assignment is fine. This pattern can be used to provide the variable
+# to older or other variants of make that don't know that variable.
+.newline?= fallback
+# expect+1: Cannot overwrite ".newline" as it is read-only
.newline= overwritten
-
-.if ${.newline} == ${NEWLINE}
-. info The .newline variable cannot be overwritten. Good.
-.else
-. info The .newline variable can be overwritten. Just don't do that.
+# expect+1: Cannot append to ".newline" as it is read-only
+.newline+= appended
+# expect+1: Cannot delete ".newline" as it is read-only
+.undef .newline
.endif
-# Restore the original value.
-.newline= ${NEWLINE}
+.if ${.newline} != ${NEWLINE}
+. error The .newline variable can be overwritten. It should be read-only.
+.endif
all:
+ @${MAKE} -f ${MAKEFILE} try-to-modify || true
@echo 'first${.newline}second'
+ @echo 'backslash newline: <${BACKSLASH_NEWLINE}>'
diff --git a/contrib/bmake/unit-tests/varname-dot-objdir.exp b/contrib/bmake/unit-tests/varname-dot-objdir.exp
index 39a9383953dd..430e2c898172 100644
--- a/contrib/bmake/unit-tests/varname-dot-objdir.exp
+++ b/contrib/bmake/unit-tests/varname-dot-objdir.exp
@@ -1 +1,2 @@
+: purge-cache was reached.
exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-objdir.mk b/contrib/bmake/unit-tests/varname-dot-objdir.mk
index e662e8ac56fa..0f53165bba41 100644
--- a/contrib/bmake/unit-tests/varname-dot-objdir.mk
+++ b/contrib/bmake/unit-tests/varname-dot-objdir.mk
@@ -1,8 +1,15 @@
-# $NetBSD: varname-dot-objdir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varname-dot-objdir.mk,v 1.3 2024/06/01 11:06:17 rillig Exp $
#
# Tests for the special .OBJDIR variable.
# TODO: Implementation
all:
- @:;
+ # Add an entry to the cached_realpath table, to test cleaning up
+ # that table in purge_relative_cached_realpaths.
+ # Having a ':=' assignment in the command line is construed but works
+ # well enough to reach the code.
+ @${MAKE} -f ${MAKEFILE} 'VAR:=$${:U.:tA}' purge-cache
+
+purge-cache:
+ : ${.TARGET} was reached.
diff --git a/contrib/bmake/unit-tests/varname-dot-parsedir.exp b/contrib/bmake/unit-tests/varname-dot-parsedir.exp
index c0bc56f41d6e..27fcf0627a81 100644
--- a/contrib/bmake/unit-tests/varname-dot-parsedir.exp
+++ b/contrib/bmake/unit-tests/varname-dot-parsedir.exp
@@ -1,5 +1,5 @@
-make: "varname-dot-parsedir.mk" line 28: At this point, .PARSEDIR is undefined.
-make: "<normalized>" line 34: The location can be faked in some cases.
-make: "varname-dot-parsedir.mk" line 38: The location is no longer fake.
+make: varname-dot-parsedir.mk:37: At this point, .PARSEDIR is undefined.
+make: <normalized>:43: The location can be faked in some cases.
+make: varname-dot-parsedir.mk:48: The location is no longer fake.
At run time, .PARSEDIR is undefined.
exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-parsedir.mk b/contrib/bmake/unit-tests/varname-dot-parsedir.mk
index 7c74419ddd10..525fdbd5636f 100644
--- a/contrib/bmake/unit-tests/varname-dot-parsedir.mk
+++ b/contrib/bmake/unit-tests/varname-dot-parsedir.mk
@@ -1,7 +1,15 @@
-# $NetBSD: varname-dot-parsedir.mk,v 1.6 2020/10/24 08:50:17 rillig Exp $
+# $NetBSD: varname-dot-parsedir.mk,v 1.8 2023/06/21 07:30:50 rillig Exp $
#
# Tests for the special .PARSEDIR variable, which contains the directory part
# of the file that is currently parsed.
+#
+# See also
+# varname-dot-includedfromdir.mk
+# varname-dot-includedfromfile.mk
+# varname-dot-parsefile.mk
+#
+# History
+# .PARSEDIR and .PARSEFILE were added on 1999-08-09.
# The .PARSEDIR may be absolute or relative, therefore there is not much that
# can be tested here.
@@ -25,6 +33,7 @@
#
# The .rawout file contains the full path to the current directory.
# In the .out file, it is filtered out.
+# expect+1: At this point, .PARSEDIR is undefined.
.info At this point, .PARSEDIR is undefined.
# There is absolutely no point in faking the location of the file that is
@@ -35,6 +44,7 @@
# After including another file, .PARSEDIR is reset.
.include "/dev/null"
+# expect+1: The location is no longer fake.
.info The location is no longer fake.
all:
diff --git a/contrib/bmake/unit-tests/varname-dot-parsefile.exp b/contrib/bmake/unit-tests/varname-dot-parsefile.exp
index b61f01c01ab7..c0262798c3b3 100644
--- a/contrib/bmake/unit-tests/varname-dot-parsefile.exp
+++ b/contrib/bmake/unit-tests/varname-dot-parsefile.exp
@@ -1,5 +1,5 @@
-make: "varname-dot-parsefile.mk" line 23: At this point, .PARSEFILE is undefined.
-make: "<normalized>" line 29: The location can be faked in some cases.
-make: "varname-dot-parsefile.mk" line 33: The location is no longer fake.
+make: varname-dot-parsefile.mk:32: At this point, .PARSEFILE is undefined.
+make: <normalized>:38: The location can be faked in some cases.
+make: varname-dot-parsefile.mk:43: The location is no longer fake.
At run time, .PARSEFILE is undefined.
exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-parsefile.mk b/contrib/bmake/unit-tests/varname-dot-parsefile.mk
index 17b48a5f77ec..0a6ed3c378e0 100644
--- a/contrib/bmake/unit-tests/varname-dot-parsefile.mk
+++ b/contrib/bmake/unit-tests/varname-dot-parsefile.mk
@@ -1,7 +1,15 @@
-# $NetBSD: varname-dot-parsefile.mk,v 1.5 2020/10/24 08:50:17 rillig Exp $
+# $NetBSD: varname-dot-parsefile.mk,v 1.7 2023/06/21 07:30:50 rillig Exp $
#
# Tests for the special .PARSEFILE variable, which contains the basename part
# of the file that is currently parsed.
+#
+# See also
+# varname-dot-includedfromdir.mk
+# varname-dot-includedfromfile.mk
+# varname-dot-parsedir.mk
+#
+# History
+# .PARSEDIR and .PARSEFILE were added on 1999-08-09.
.if ${.PARSEFILE} != "varname-dot-parsefile.mk"
. error
@@ -20,6 +28,7 @@
# The variable .PARSEFILE is indirectly used by the .info directive,
# via PrintLocation.
+# expect+1: At this point, .PARSEFILE is undefined.
.info At this point, .PARSEFILE is undefined.
# There is absolutely no point in faking the location of the file that is
@@ -30,6 +39,7 @@
# After including another file, .PARSEFILE is reset.
.include "/dev/null"
+# expect+1: The location is no longer fake.
.info The location is no longer fake.
all:
diff --git a/contrib/bmake/unit-tests/varname-dot-shell.exp b/contrib/bmake/unit-tests/varname-dot-shell.exp
index 800e8a375761..7cc17435895b 100755
--- a/contrib/bmake/unit-tests/varname-dot-shell.exp
+++ b/contrib/bmake/unit-tests/varname-dot-shell.exp
@@ -1,31 +1,34 @@
-Parsing line 10: ORIG_SHELL:= ${.SHELL}
+Parsing varname-dot-shell.mk:10: ORIG_SHELL:= ${.SHELL}
Global: ORIG_SHELL = # (empty)
Var_Parse: ${.SHELL} (eval-keep-dollar-and-undefined)
-Global: delete .SHELL (not found)
+Global: ignoring delete '.SHELL' as it is not found
Command: .SHELL = (details omitted)
Global: ORIG_SHELL = (details omitted)
-Parsing line 12: .SHELL= overwritten
-Global: .SHELL = overwritten
+Parsing varname-dot-shell.mk:12: .SHELL= overwritten
+Global: ignoring '.SHELL = overwritten' due to a command line variable of the same name
+Parsing varname-dot-shell.mk:13: .if ${.SHELL} != ${ORIG_SHELL}
CondParser_Eval: ${.SHELL} != ${ORIG_SHELL}
-Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined)
-Var_Parse: ${ORIG_SHELL} (eval-defined)
+Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined-loud)
+Var_Parse: ${ORIG_SHELL} (eval-defined-loud)
Comparing "(details omitted)" != "(details omitted)"
-Parsing line 19: .MAKEFLAGS: .SHELL+=appended
+Parsing varname-dot-shell.mk:19: .MAKEFLAGS: .SHELL+=appended
ParseDependency(.MAKEFLAGS: .SHELL+=appended)
-Ignoring append to .SHELL since it is read-only
+Command: ignoring '.SHELL += appended' as it is read-only
+Parsing varname-dot-shell.mk:20: .if ${.SHELL} != ${ORIG_SHELL}
CondParser_Eval: ${.SHELL} != ${ORIG_SHELL}
-Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined)
-Var_Parse: ${ORIG_SHELL} (eval-defined)
+Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined-loud)
+Var_Parse: ${ORIG_SHELL} (eval-defined-loud)
Comparing "(details omitted)" != "(details omitted)"
-Parsing line 27: .undef .SHELL
-Global: delete .SHELL
-Parsing line 28: .SHELL= newly overwritten
-Global: .SHELL = newly overwritten
+Parsing varname-dot-shell.mk:27: .undef .SHELL
+Global: ignoring delete '.SHELL' as it is not found
+Parsing varname-dot-shell.mk:28: .SHELL= newly overwritten
+Global: ignoring '.SHELL = newly overwritten' due to a command line variable of the same name
+Parsing varname-dot-shell.mk:29: .if ${.SHELL} != ${ORIG_SHELL}
CondParser_Eval: ${.SHELL} != ${ORIG_SHELL}
-Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined)
-Var_Parse: ${ORIG_SHELL} (eval-defined)
+Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined-loud)
+Var_Parse: ${ORIG_SHELL} (eval-defined-loud)
Comparing "(details omitted)" != "(details omitted)"
-Parsing line 33: .MAKEFLAGS: -d0
+Parsing varname-dot-shell.mk: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.exp b/contrib/bmake/unit-tests/varname-dot-suffixes.exp
index 186b5f06c227..96f68d245d54 100644
--- a/contrib/bmake/unit-tests/varname-dot-suffixes.exp
+++ b/contrib/bmake/unit-tests/varname-dot-suffixes.exp
@@ -1,39 +1,39 @@
-Global: delete .SUFFIXES (not found)
+Global: ignoring delete '.SUFFIXES' as it is not found
Global: .MAKEFLAGS = -r -k -d v -d
Global: .MAKEFLAGS = -r -k -d v -d 0
-Global: .SUFFIXES = set ignored (read-only)
-Global: .SUFFIXES = append ignored (read-only)
+Global: ignoring '.SUFFIXES = set' as it is read-only
+Global: ignoring '.SUFFIXES = append' as it is read-only
Global: _ = # (empty)
Var_Parse: ${.SUFFIXES::=assign} (eval-keep-dollar-and-undefined)
Evaluating modifier ${.SUFFIXES::...} on value ".c .o .1 .err .tar.gz" (eval-keep-dollar-and-undefined, regular)
Modifier part: "assign"
-Global: .SUFFIXES = assign ignored (read-only)
+Global: ignoring '.SUFFIXES = assign' as it is read-only
Result of ${.SUFFIXES::=assign} is "" (eval-keep-dollar-and-undefined, regular)
Global: _ = # (empty)
Var_Parse: ${preserve:L:_=.SUFFIXES} (eval-keep-dollar-and-undefined)
Evaluating modifier ${preserve:L} on value "" (eval-keep-dollar-and-undefined, undefined)
Result of ${preserve:L} is "preserve" (eval-keep-dollar-and-undefined, defined)
Evaluating modifier ${preserve:_...} on value "preserve" (eval-keep-dollar-and-undefined, defined)
-Global: .SUFFIXES = preserve ignored (read-only)
+Global: ignoring '.SUFFIXES = preserve' as it is read-only
Result of ${preserve:_=.SUFFIXES} is "preserve" (eval-keep-dollar-and-undefined, defined)
Global: _ = preserve
Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d
Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d 0
-Var_Parse: ${1 2:L:@.SUFFIXES@${.SUFFIXES}@} != ".c .o .1 .err .tar.gz .c .o .1 .err .tar.gz" (eval-defined)
-Evaluating modifier ${1 2:L} on value "" (eval-defined, undefined)
-Result of ${1 2:L} is "1 2" (eval-defined, defined)
-Evaluating modifier ${1 2:@...} on value "1 2" (eval-defined, defined)
+Var_Parse: ${1 2:L:@.SUFFIXES@${.SUFFIXES}@} != ".c .o .1 .err .tar.gz .c .o .1 .err .tar.gz" (eval-defined-loud)
+Evaluating modifier ${1 2:L} on value "" (eval, undefined)
+Result of ${1 2:L} is "1 2" (eval, defined)
+Evaluating modifier ${1 2:@...} on value "1 2" (eval, defined)
Modifier part: ".SUFFIXES"
Modifier part: "${.SUFFIXES}"
ModifyWords: split "1 2" into 2 words
-Command: .SUFFIXES = 1 ignored (read-only)
-Var_Parse: ${.SUFFIXES} (eval-defined)
-ModifyWord_Loop: in "1", replace ".SUFFIXES" with "${.SUFFIXES}" to ".c .o .1 .err .tar.gz"
-Command: .SUFFIXES = 2 ignored (read-only)
-Var_Parse: ${.SUFFIXES} (eval-defined)
-ModifyWord_Loop: in "2", replace ".SUFFIXES" with "${.SUFFIXES}" to ".c .o .1 .err .tar.gz"
-Command: delete .SUFFIXES (not found)
-Result of ${1 2:@.SUFFIXES@${.SUFFIXES}@} is ".c .o .1 .err .tar.gz .c .o .1 .err .tar.gz" (eval-defined, defined)
+Command: ignoring '.SUFFIXES = 1' as it is read-only
+Var_Parse: ${.SUFFIXES} (eval)
+ModifyWord_Loop: expand "${.SUFFIXES}" to ".c .o .1 .err .tar.gz"
+Command: ignoring '.SUFFIXES = 2' as it is read-only
+Var_Parse: ${.SUFFIXES} (eval)
+ModifyWord_Loop: expand "${.SUFFIXES}" to ".c .o .1 .err .tar.gz"
+Command: ignoring delete '.SUFFIXES' as it is not found
+Result of ${1 2:@.SUFFIXES@${.SUFFIXES}@} is ".c .o .1 .err .tar.gz .c .o .1 .err .tar.gz" (eval, defined)
Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d 0 -d v -d
Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d 0 -d v -d 0
exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-suffixes.mk b/contrib/bmake/unit-tests/varname-dot-suffixes.mk
index f9f995fcd845..27521f621cb0 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.3 2022/04/15 09:33:20 rillig Exp $
+# $NetBSD: varname-dot-suffixes.mk,v 1.5 2023/12/20 09:03:09 rillig Exp $
#
# Tests for the special "variable" .SUFFIXES, which lists the suffixes that
# have been registered for use in suffix transformation rules. Suffixes are
@@ -51,7 +51,7 @@
# Deleting .SUFFIXES has no effect since there is no actual variable of that
# name.
.MAKEFLAGS: -dv
-# expect: Global: delete .SUFFIXES (not found)
+# expect: Global: ignoring delete '.SUFFIXES' as it is not found
.undef .SUFFIXES
.MAKEFLAGS: -d0
.if ${.SUFFIXES} != ".c .o .1 .err .tar.gz"
@@ -61,13 +61,13 @@
# The list of suffixes can only be modified using dependency declarations, any
# attempt at setting the variable named '.SUFFIXES' is rejected.
.MAKEFLAGS: -dv
-# expect: Global: .SUFFIXES = set ignored (read-only)
+# expect: Global: ignoring '.SUFFIXES = set' as it is read-only
.SUFFIXES= set
-# expect: Global: .SUFFIXES = append ignored (read-only)
+# expect: Global: ignoring '.SUFFIXES = append' as it is read-only
.SUFFIXES+= append
-# expect: Global: .SUFFIXES = assign ignored (read-only)
+# expect: Global: ignoring '.SUFFIXES = assign' as it is read-only
_:= ${.SUFFIXES::=assign}
-# expect: Global: .SUFFIXES = preserve ignored (read-only)
+# expect: Global: ignoring '.SUFFIXES = preserve' as it is read-only
_:= ${preserve:L:_=.SUFFIXES}
.MAKEFLAGS: -d0
@@ -94,10 +94,9 @@ _:= ${preserve:L:_=.SUFFIXES}
# the name would be '.SUFFIXES.', furthermore the name of the iteration
# variable is typically in singular form.
.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)
+# expect: Command: ignoring '.SUFFIXES = 1' as it is read-only
+# expect: Command: ignoring '.SUFFIXES = 2' as it is read-only
+# expect: Command: ignoring delete '.SUFFIXES' as it is 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 a861ba378cef..2165784933e4 100644
--- a/contrib/bmake/unit-tests/varname-empty.exp
+++ b/contrib/bmake/unit-tests/varname-empty.exp
@@ -1,22 +1,22 @@
-Var_SetExpand: variable name "${:U}" expands to empty string, with value "cmdline-u" - ignored
-Var_SetExpand: variable name "" expands to empty string, with value "cmdline-plain" - ignored
+Command: ignoring ' = cmdline-u' as the variable name '${:U}' expands to empty
+Command: ignoring ' = cmdline-plain' as the variable name '' expands to empty
Global: .CURDIR = <curdir>
Var_Parse: ${MAKE_OBJDIR_CHECK_WRITABLE} (eval)
Global: .TARGETS = # (empty)
Internal: MAKEFILE = varname-empty.mk
Global: .MAKE.MAKEFILES = varname-empty.mk
Global: .PARSEFILE = varname-empty.mk
-Global: delete .INCLUDEDFROMDIR (not found)
-Global: delete .INCLUDEDFROMFILE (not found)
-Var_SetExpand: variable name "" expands to empty string, with value "default" - ignored
-Var_SetExpand: variable name "" expands to empty string, with value "assigned" - ignored
-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
+Global: ignoring delete '.INCLUDEDFROMDIR' as it is not found
+Global: ignoring delete '.INCLUDEDFROMFILE' as it is not found
+Global: ignoring ' = default' as the variable name '' expands to empty
+Global: ignoring ' = assigned' as the variable name '' expands to empty
+Global: ignoring ' = appended' as the variable name is empty
+Global: ignoring ' = ' as the variable name '' expands to empty
+Global: ignoring ' = subst' as the variable name '' expands to empty
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
+Global: ignoring ' = shell-output' as the variable name '' expands to empty
+Global: ignoring ' = assigned indirectly' as the variable name '${:U}' expands to empty
+Global: ignoring ' += appended indirectly' as the variable name '${:U}' expands to empty
Global: .MAKEFLAGS = -r -d v -d
Global: .MAKEFLAGS = -r -d v -d 0
out: fallback
diff --git a/contrib/bmake/unit-tests/varname-empty.mk b/contrib/bmake/unit-tests/varname-empty.mk
index f077d2ec07b4..e018a5d44894 100755
--- a/contrib/bmake/unit-tests/varname-empty.mk
+++ b/contrib/bmake/unit-tests/varname-empty.mk
@@ -1,9 +1,9 @@
-# $NetBSD: varname-empty.mk,v 1.9 2021/04/04 10:13:09 rillig Exp $
+# $NetBSD: varname-empty.mk,v 1.10 2023/11/19 21:47:52 rillig Exp $
#
# Tests for the special variable with the empty name.
#
# There is no variable named "" at all, and this fact is used a lot in
-# variable expressions of the form ${:Ufallback}. These expressions are
+# expressions of the form ${:Ufallback}. These expressions are
# based on the variable named "" and use the :U modifier to assign a
# fallback value to the expression (but not to the variable).
#
diff --git a/contrib/bmake/unit-tests/varname-make_print_var_on_error-jobs.exp b/contrib/bmake/unit-tests/varname-make_print_var_on_error-jobs.exp
index 81bea0e99ae9..abb8448be90e 100644
--- a/contrib/bmake/unit-tests/varname-make_print_var_on_error-jobs.exp
+++ b/contrib/bmake/unit-tests/varname-make_print_var_on_error-jobs.exp
@@ -2,7 +2,7 @@ echo fail all; false 'all' '${.TARGET}' '$${.TARGET}'
fail all
*** [all] Error code 1
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
.ERROR_TARGET='all'
.ERROR_CMD='@: before '${.TARGET}' '${.TARGET}' '$${.TARGET}' echo fail ${.TARGET}; false '${.TARGET}' '${.TARGET}' '$${.TARGET}' @: after '${.TARGET}' '${.TARGET}' '$${.TARGET}''
exit status 1
diff --git a/contrib/bmake/unit-tests/varname-make_print_var_on_error-jobs.mk b/contrib/bmake/unit-tests/varname-make_print_var_on_error-jobs.mk
index 10a9647fbd1e..b422f25ff12e 100644
--- a/contrib/bmake/unit-tests/varname-make_print_var_on_error-jobs.mk
+++ b/contrib/bmake/unit-tests/varname-make_print_var_on_error-jobs.mk
@@ -1,9 +1,9 @@
-# $NetBSD: varname-make_print_var_on_error-jobs.mk,v 1.3 2021/02/04 21:33:14 rillig Exp $
+# $NetBSD: varname-make_print_var_on_error-jobs.mk,v 1.4 2023/11/19 22:32:44 rillig Exp $
#
# Tests for the special MAKE_PRINT_VAR_ON_ERROR variable, which prints the
# values of selected variables on error.
#
-# The variable .ERROR_CMD contains all commands of the target, with variable
+# The variable .ERROR_CMD contains all commands of the target, with
# expressions expanded, just as they were printed to the shell command file.
#
# The commands in .ERROR_CMD are space-separated. Since each command usually
diff --git a/contrib/bmake/unit-tests/varname-make_print_var_on_error.exp b/contrib/bmake/unit-tests/varname-make_print_var_on_error.exp
index f23deb3568d6..5ced518b3e3f 100644
--- a/contrib/bmake/unit-tests/varname-make_print_var_on_error.exp
+++ b/contrib/bmake/unit-tests/varname-make_print_var_on_error.exp
@@ -3,7 +3,7 @@ fail all
*** Error code 1 (continuing)
Stop.
-make: stopped in unit-tests
+make: stopped making "all" in unit-tests
.ERROR_TARGET='all'
.ERROR_CMD=''
exit status 1
diff --git a/contrib/bmake/unit-tests/varname-make_stack_trace.exp b/contrib/bmake/unit-tests/varname-make_stack_trace.exp
new file mode 100644
index 000000000000..c0f46cc5aa1e
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-make_stack_trace.exp
@@ -0,0 +1,40 @@
+make: Unknown modifier ":Z"
+ while evaluating "${:Z}" with value ""
+ in command "@echo ${:Z}"
+ in target "provoke-error"
+ in make[2] in directory "<curdir>"
+*** Error code 2 (continuing)
+
+Stop.
+make: stopped making "disabled-compat" in unit-tests
+make: Unknown modifier ":Z"
+ while evaluating "${:Z}" with value ""
+ in command "@echo ${:Z}"
+ in target "provoke-error"
+ in make[2] in directory "<curdir>"
+*** [disabled-parallel] Error code 2
+
+make: stopped making "disabled-parallel" in unit-tests
+make: Unknown modifier ":Z"
+ while evaluating "${:Z}" with value ""
+ in command "@echo ${:Z}"
+ in target "provoke-error"
+ in make[2] in directory "<curdir>"
+ in command "make -f varname-make_stack_trace.mk provoke-error"
+ in target "enabled-compat"
+ in make[1] in directory "<curdir>"
+*** Error code 2 (continuing)
+
+Stop.
+make: stopped making "enabled-compat" in unit-tests
+make: Unknown modifier ":Z"
+ while evaluating "${:Z}" with value ""
+ in command "@echo ${:Z}"
+ in target "provoke-error"
+ in make[2] in directory "<curdir>"
+ in target "enabled-parallel"
+ in make[1] in directory "<curdir>"
+*** [enabled-parallel] Error code 2
+
+make: stopped making "enabled-parallel" in unit-tests
+exit status 0
diff --git a/contrib/bmake/unit-tests/varname-make_stack_trace.mk b/contrib/bmake/unit-tests/varname-make_stack_trace.mk
new file mode 100644
index 000000000000..cba02559bafe
--- /dev/null
+++ b/contrib/bmake/unit-tests/varname-make_stack_trace.mk
@@ -0,0 +1,37 @@
+# $NetBSD: varname-make_stack_trace.mk,v 1.1 2025/06/13 03:51:18 rillig Exp $
+#
+# Tests for the MAKE_STACK_TRACE environment variable, which controls whether
+# to print inter-process stack traces that are useful to narrow down where an
+# erroneous expression comes from.
+#
+# While inter-process stack traces are useful to narrow down errors, they are
+# disabled by default since the stack trace is stored in an environment
+# variable and a stack trace can grow large depending on the shell commands in
+# the sub-make processes. The space used for the stack traces would compete
+# with the space for the command line arguments, and long command lines are
+# already written to a temporary file by Cmd_Exec to not overwhelm this space.
+
+all: .PHONY
+ @${MAKE} -f ${MAKEFILE} disabled-compat || :
+ @${MAKE} -f ${MAKEFILE} -j1 disabled-parallel || :
+ @MAKE_STACK_TRACE=yes ${MAKE} -f ${MAKEFILE} enabled-compat || :
+ @MAKE_STACK_TRACE=yes ${MAKE} -f ${MAKEFILE} -j1 enabled-parallel || :
+
+# expect-not: in target "disabled-compat"
+disabled-compat: .PHONY
+ @${MAKE} -f ${MAKEFILE} provoke-error
+
+# expect-not: in target "disabled-parallel"
+disabled-parallel: .PHONY
+ @${MAKE} -f ${MAKEFILE} provoke-error
+
+# expect: in target "enabled-compat"
+enabled-compat: .PHONY
+ @${MAKE} -f ${MAKEFILE} provoke-error
+
+# expect: in target "enabled-parallel"
+enabled-parallel: .PHONY
+ @${MAKE} -f ${MAKEFILE} provoke-error
+
+provoke-error: .PHONY
+ @echo ${:Z}
diff --git a/contrib/bmake/unit-tests/varname-makeflags.exp b/contrib/bmake/unit-tests/varname-makeflags.exp
index 39a9383953dd..c1354177ca47 100644
--- a/contrib/bmake/unit-tests/varname-makeflags.exp
+++ b/contrib/bmake/unit-tests/varname-makeflags.exp
@@ -1 +1,21 @@
+spaces_stage_0: MAKEFLAGS=< -r -k >
+spaces_stage_0: env MAKEFLAGS=< -r -k >
+spaces_stage_1: MAKEFLAGS=< -r -k -d 00000 -D VARNAME WITH SPACES >
+spaces_stage_1: env MAKEFLAGS=< -r -k -d 00000 -D VARNAME WITH SPACES >
+dollars_stage_0: MAKEFLAGS=< -r -k >
+dollars_stage_1: env MAKEFLAGS=< -r -k DOLLARS=\$\{varname\}>
+dollars_stage_1: MAKEFLAGS=< -r -k DOLLARS=\{varname\}>
+dollars_stage_1: MAKEFLAGS:q=< -r -k DOLLARS=\{varname\}>
+dollars_stage_2: env MAKEFLAGS=< -r -k DOLLARS=>
+dollars_stage_2: dollars=<>
+dollars_stage_2: MAKEFLAGS=< -r -k DOLLARS=>
+dollars_stage_3: env MAKEFLAGS=< -r -k DOLLARS=>
+dollars_stage_3: dollars=<>
+dollars_stage_3: MAKEFLAGS=< -r -k DOLLARS=>
+append_stage_0: MAKEFLAGS=< -r -k >
+append_stage_1: MAKEFLAGS=< -r -k -D before-0 -D after-0 VAR0=value>
+append_stage_2: MAKEFLAGS=< -r -k -D before-0 -D after-0 -D before-1 -D after-1 VAR0=value VAR1=value>
+append_stage_3: MAKEFLAGS=< -r -k -D before-0 -D after-0 -D before-1 -D after-1 -D before-2 -D after-2 VAR0=value VAR1=value VAR2=value>
+override_stage_1: run MAKEFLAGS=< -r -k STAGE=1 VAR=value>
+override_stage_2: STAGE=<2> VAR=<value>
exit status 0
diff --git a/contrib/bmake/unit-tests/varname-makeflags.mk b/contrib/bmake/unit-tests/varname-makeflags.mk
index f7840c2eb7a5..4173c5a92095 100644
--- a/contrib/bmake/unit-tests/varname-makeflags.mk
+++ b/contrib/bmake/unit-tests/varname-makeflags.mk
@@ -1,44 +1,182 @@
-# $NetBSD: varname-makeflags.mk,v 1.5 2022/01/16 18:16:06 sjg Exp $
+# $NetBSD: varname-makeflags.mk,v 1.8 2023/06/01 07:27:30 rillig Exp $
#
-# Tests for the special MAKEFLAGS variable, which is basically just a normal
-# environment variable. It is closely related to .MAKEFLAGS but captures the
-# state of .MAKEFLAGS at the very beginning of make, before any makefiles are
-# read.
+# Tests for the environment variable 'MAKEFLAGS', from which additional
+# command line arguments are read before the actual command line arguments.
+#
+# After reading the makefiles and before making the targets, the arguments
+# that were collected in '.MAKEFLAGS' and '.MAKEOVERRIDES' are written back to
+# the environment variable 'MAKEFLAGS'.
+
+all: spaces_stage_0 dollars_stage_0 append_stage_0 override_stage_0
-# TODO: Implementation
-.MAKEFLAGS: -d0
+.if !make(*stage*)
# The unit tests are run with an almost empty environment. In particular,
-# the variable MAKEFLAGS is not set. The '.MAKEFLAGS:' above also doesn't
-# influence the environment variable MAKEFLAGS, therefore it is still
-# undefined at this point.
-.if ${MAKEFLAGS:Uundefined} != "undefined"
-. error
-.endif
+# the variable MAKEFLAGS is not set.
+. if ${MAKEFLAGS:Uundefined} != "undefined"
+. error
+. endif
# The special variable .MAKEFLAGS is influenced though.
# See varname-dot-makeflags.mk for more details.
-.if ${.MAKEFLAGS} != " -r -k -d 0"
-. error
-.endif
+. if ${.MAKEFLAGS} != " -r -k"
+. 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
+. 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}
+. if ${OUTPUT} != " -r -V .MAKEFLAGS"
+. error ${OUTPUT}
+. endif
+
+.endif
+
+
+# When options are parsed, the option and its argument are appended as
+# separate words to the MAKEFLAGS for the child processes. Special characters
+# in the option arguments are not quoted though.
+spaces_stage_0:
+ @echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
+ @echo "$@: env MAKEFLAGS=<$$MAKEFLAGS>"
+ @${MAKE} -f ${MAKEFILE} spaces_stage_1 -d00000 -D"VARNAME WITH SPACES"
+
+# At this point, the 'VARNAME WITH SPACES' is no longer recognizable as a
+# single command line argument. In practice, variable names don't contain
+# spaces.
+spaces_stage_1:
+ @echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
+ @echo "$@: env MAKEFLAGS=<$$MAKEFLAGS>"
+
+
+# Demonstrate that '$' characters are altered when they are passed on to child
+# make processes via MAKEFLAGS.
+dollars_stage_0:
+ @echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
+
+ # The '$$$$' becomes a literal '$$' when building the '${MAKE}'
+ # command line, making the actual argument 'DOLLARS=$${varname}'.
+ # At this stage, MAKEFLAGS is not yet involved.
+ @${MAKE} -f ${MAKEFILE} dollars_stage_1 DOLLARS='$$$${varname}'
+
+.if make(dollars_stage_1)
+# At this point, the variable 'DOLLARS' contains '$${varname}', which
+# evaluates to a literal '$' followed by '{varname}'.
+. if ${DOLLARS} != "\${varname}"
+. error
+. endif
.endif
+dollars_stage_1:
+ # At this point, the stage 1 make provides the environment variable
+ # 'MAKEFLAGS' to its child processes, even if the child process is not
+ # another make.
+ #
+ # expect: dollars_stage_1: env MAKEFLAGS=< -r -k DOLLARS=\$\{varname\}>
+ #
+ # The 'DOLLARS=\$\{varname\}' assignment is escaped so that the stage
+ # 2 make will see it as a single word.
+ @echo "$@: env MAKEFLAGS=<$$MAKEFLAGS>"
+ # At this point, evaluating the environment variable 'MAKEFLAGS' leads
+ # to strange side effects as the string '\$\{varname\}' is interpreted
+ # as:
+ #
+ # \ a literal string of a single backslash
+ # $\ the value of the variable named '\'
+ # {varname\} a literal string
+ #
+ # Since the variable named '\' is not defined, the resulting value is
+ # '\{varname\}'. Make doesn't handle isolated '$' characters in
+ # strings well, instead each '$' has to be part of a '$$' or be part
+ # of a subexpression like '${VAR}'.
+ @echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
+
+ # The modifier ':q' preserves a '$$' in an expression value instead of
+ # expanding it to a single '$', but it's already too late, as that
+ # modifier applies after the expression has been evaluated. Except
+ # for debug logging, there is no way to process strings that contain
+ # isolated '$'.
+ @echo '$@: MAKEFLAGS:q=<'${MAKEFLAGS:q}'>'
+
+ @${MAKE} -f ${MAKEFILE} dollars_stage_2
+
+.if make(dollars_stage_2)
+# At this point, the variable 'DOLLARS' contains '${varname}', and since
+# 'varname' is undefined, that expression evaluates to an empty string.
+. if ${DOLLARS} != ""
+. error
+. endif
+varname= varvalue
+. if ${DOLLARS} != "varvalue"
+. error
+. endif
+. undef varname
+.endif
+dollars_stage_2:
+ @echo "$@: env MAKEFLAGS=<$$MAKEFLAGS>"
+ @echo '$@: dollars=<'${DOLLARS:Q}'>'
+ @echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
+ @${MAKE} -f ${MAKEFILE} dollars_stage_3
+
+dollars_stage_3:
+ @echo "$@: env MAKEFLAGS=<$$MAKEFLAGS>"
+ @echo '$@: dollars=<'${DOLLARS:Uundefined:Q}'>'
+ @echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
+
+
+# Demonstrates in which exact order the MAKEFLAGS are built from the parent
+# MAKEFLAGS and the flags from the command line, in particular that variable
+# assignments are passed at the end, after the options.
+append_stage_0:
+ @echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
+ @${MAKE} -Dbefore-0 -f ${MAKEFILE} append_stage_1 VAR0=value -Dafter-0
+
+append_stage_1:
+ @echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
+ @${MAKE} -Dbefore-1 -f ${MAKEFILE} append_stage_2 VAR1=value -Dafter-1
+
+append_stage_2:
+ @echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
+ @${MAKE} -Dbefore-2 -f ${MAKEFILE} append_stage_3 VAR2=value -Dafter-2
+
+append_stage_3:
+ @echo '$@: MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
+
+
+# Demonstrates the implementation details of 'MAKEFLAGS', in particular that
+# it is an environment variable rather than a global variable.
+override_stage_0:
+ @${MAKE} -f ${MAKEFILE} STAGE=1 VAR=value override_stage_1
+
+.if make(override_stage_1)
+# While parsing the makefiles, 'MAKEFLAGS' is the value of the environment
+# variable, in this case provided by stage 0.
+. if ${MAKEFLAGS:M*} != "-r -k"
+. error
+. endif
+MAKEFLAGS= overridden # temporarily override it
+. if ${MAKEFLAGS} != "overridden"
+. error
+. endif
+.undef MAKEFLAGS # make the environment variable visible again
+. if ${MAKEFLAGS:M*} != "-r -k"
+. error
+. endif
+.endif
+override_stage_1:
+ @echo '$@: run MAKEFLAGS=<'${MAKEFLAGS:Q}'>'
+ @${MAKE} -f ${MAKEFILE} STAGE=2 override_stage_2
-all:
+override_stage_2:
+ @echo '$@: STAGE=<${STAGE}> VAR=<${VAR}>'
diff --git a/contrib/bmake/unit-tests/varname-vpath.exp b/contrib/bmake/unit-tests/varname-vpath.exp
index bf7a3036e99d..a750957b92ac 100644
--- a/contrib/bmake/unit-tests/varname-vpath.exp
+++ b/contrib/bmake/unit-tests/varname-vpath.exp
@@ -1,12 +1,12 @@
CondParser_Eval: !defined(TEST_MAIN)
CondParser_Eval: exists(file-in-subdirectory)
-exists(file-in-subdirectory) result is ""
+"file-in-subdirectory" does not exist
CondParser_Eval: exists(file2-in-subdirectory)
-exists(file2-in-subdirectory) result is ""
+"file2-in-subdirectory" does not exist
CondParser_Eval: exists(file-in-subdirectory)
-exists(file-in-subdirectory) result is "varname-vpath.dir/file-in-subdirectory"
+"file-in-subdirectory" exists in "varname-vpath.dir/file-in-subdirectory"
: yes 1
CondParser_Eval: exists(file2-in-subdirectory)
-exists(file2-in-subdirectory) result is "varname-vpath.dir2/file2-in-subdirectory"
+"file2-in-subdirectory" exists in "varname-vpath.dir2/file2-in-subdirectory"
: yes 2
exit status 0
diff --git a/contrib/bmake/unit-tests/varname.exp b/contrib/bmake/unit-tests/varname.exp
index 942532b654d5..454e40cdf9cf 100644
--- a/contrib/bmake/unit-tests/varname.exp
+++ b/contrib/bmake/unit-tests/varname.exp
@@ -5,17 +5,26 @@ Var_Parse: ${VARNAME} (eval)
Global: VAR((( = 3 open parentheses
Var_Parse: ${VAR(((}}}}" != "3 open parentheses}}}" (eval)
Global: .ALLTARGETS = VAR(((=)
-make: "varname.mk" line 30: No closing parenthesis in archive specification
-make: "varname.mk" line 30: Error in archive specification: "VAR"
-Var_Parse: ${:UVAR\(\(\(}= try2 (eval-defined)
-Evaluating modifier ${:U...} on value "" (eval-defined, undefined)
-Result of ${:UVAR\(\(\(} is "VAR\(\(\(" (eval-defined, defined)
+make: varname.mk:31: Missing ")" in archive specification
+make: varname.mk:31: Error in archive specification: "VAR"
+Var_Parse: ${:UVAR\(\(\(}= try2 (eval)
+Evaluating modifier ${:U...} on value "" (eval, undefined)
+Result of ${:UVAR\(\(\(} is "VAR\(\(\(" (eval, defined)
Global: .ALLTARGETS = VAR(((=) VAR\(\(\(=
-make: "varname.mk" line 35: Invalid line type
+make: varname.mk:37: Invalid line "${:UVAR\(\(\(}= try2", expanded to "VAR\(\(\(= try2"
Var_Parse: ${VARNAME} (eval)
Global: VAR((( = try3
Global: .MAKEFLAGS = -r -k -d v -d
Global: .MAKEFLAGS = -r -k -d v -d 0
+make: varname.mk:98: warning: Invalid character " " in variable name "if ,yes,no"
+make: varname.mk:113: warning: Invalid character " " in variable name "if ,yes,no"
+make: varname.mk:120: warning: Invalid character " " in variable name "if ,,"
+make: varname.mk:128: Unknown modifier ":yes,answer"
+ while evaluating variable "if ,answer" with value ""
+ while evaluating variable "GNU_MAKE_IF_MODIFIER" with value "$(if ${HAVE_STRLEN},answer:yes,answer:no)"
+make: varname.mk:138: warning: Invalid character "\x09" in variable name "a b"
+make: varname.mk:138: Variable "a b" is undefined
+make: varname.mk:144: Variable "ÄÖÜ" is undefined
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varname.mk b/contrib/bmake/unit-tests/varname.mk
index e86fd9176a2a..17f29da7ef4c 100644
--- a/contrib/bmake/unit-tests/varname.mk
+++ b/contrib/bmake/unit-tests/varname.mk
@@ -1,18 +1,17 @@
-# $NetBSD: varname.mk,v 1.10 2022/02/09 21:09:24 rillig Exp $
+# $NetBSD: varname.mk,v 1.18 2025/06/28 22:39:29 rillig Exp $
#
-# Tests for special variables, such as .MAKE or .PARSEDIR.
-# And for variable names in general.
+# Tests for variable names.
.MAKEFLAGS: -dv
-# In variable names, braces are allowed, but they must be balanced.
-# Parentheses and braces may be mixed.
+# In a variable assignment, braces are allowed in the variable name, but they
+# must be balanced. Parentheses and braces may be mixed.
VAR{{{}}}= 3 braces
.if "${VAR{{{}}}}" != "3 braces"
. error
.endif
-# In variable expressions, the parser works differently. It doesn't treat
+# In expressions, the parser works differently. It doesn't treat
# braces and parentheses equally, therefore the first closing brace already
# marks the end of the variable name.
VARNAME= VAR(((
@@ -27,11 +26,14 @@ ${VARNAME}= 3 open parentheses
# This is not a variable assignment since the parentheses and braces are not
# balanced. At the end of the line, there are still 3 levels open, which
# means the variable name is not finished.
+# expect+2: Missing ")" in archive specification
+# expect+1: Error in archive specification: "VAR"
${:UVAR(((}= try1
# On the left-hand side of a variable assignments, the backslash is not parsed
# as an escape character, therefore the parentheses still count to the nesting
# level, which at the end of the line is still 3. Therefore this is not a
# variable assignment as well.
+# expect+1: Invalid line "${:UVAR\(\(\(}= try2", expanded to "VAR\(\(\(= try2"
${:UVAR\(\(\(}= try2
# To assign to a variable with an arbitrary name, the variable name has to
# come from an external source, not the text that is parsed in the assignment
@@ -83,4 +85,61 @@ ASDZguv.param= once
. error
.endif
-all:
+
+# Warn about expressions in the style of GNU make, as these would silently
+# expand to an empty string instead.
+#
+# https://pubs.opengroup.org/onlinepubs/9799919799/utilities/make.html says:
+# a macro name shall not contain an <equals-sign>, <blank>, or control
+# character.
+#
+GNU_MAKE_IF= $(if ${HAVE_STRLEN},yes,no)
+# expect+1: warning: Invalid character " " in variable name "if ,yes,no"
+.if ${GNU_MAKE_IF} != ""
+. error
+.endif
+#
+# This requirement needs to be ignored for expressions with a ":L" or ":?:"
+# modifier, as these modifiers rely on arbitrary characters in the expression
+# name.
+.if ${"left" == "right":?equal:unequal} != "unequal"
+. error
+.endif
+#
+# In fact, this requirement is ignored for any expression that has a modifier.
+# In this indirect case, though, the expression with the space in the name is
+# a nested expression, so the ":U" modifier doesn't affect the warning.
+# expect+1: warning: Invalid character " " in variable name "if ,yes,no"
+.if ${GNU_MAKE_IF:Ufallback} != ""
+. error
+.endif
+#
+# A modifier in a nested expression does not affect the warning.
+GNU_MAKE_IF_EXPR= $(if ${HAVE_STRLEN},${HEADERS:.h=.c},)
+# expect+1: warning: Invalid character " " in variable name "if ,,"
+.if ${GNU_MAKE_IF_EXPR} != ""
+. error
+.endif
+#
+# When the GNU make expression contains a colon, chances are good that the
+# colon is interpreted as an unknown modifier.
+GNU_MAKE_IF_MODIFIER= $(if ${HAVE_STRLEN},answer:yes,answer:no)
+# expect+1: Unknown modifier ":yes,answer"
+.if ${GNU_MAKE_IF_MODIFIER} != "no)"
+. error
+.endif
+#
+# If the variable name contains a non-printable character, the warning
+# contains the numeric character value instead, to prevent control sequences
+# in the output.
+CONTROL_CHARACTER= ${:U a b:ts\t}
+# expect+2: warning: Invalid character "\x09" in variable name "a b"
+# expect+1: Variable "a b" is undefined
+.if ${${CONTROL_CHARACTER}} != ""
+.endif
+#
+# For now, only whitespace generates a warning, non-ASCII characters don't.
+UMLAUT= ÄÖÜ
+# expect+1: Variable "ÄÖÜ" is undefined
+.if ${${UMLAUT}} != ""
+.endif
diff --git a/contrib/bmake/unit-tests/varparse-dynamic.exp b/contrib/bmake/unit-tests/varparse-dynamic.exp
index a2ff29413167..22cc15b41307 100644
--- a/contrib/bmake/unit-tests/varparse-dynamic.exp
+++ b/contrib/bmake/unit-tests/varparse-dynamic.exp
@@ -1,5 +1,5 @@
-make: "varparse-dynamic.mk" line 8: Malformed conditional (${.TARGEX})
-make: "varparse-dynamic.mk" line 10: Malformed conditional (${.TARGXX})
+make: varparse-dynamic.mk:9: Variable ".TARGEX" is undefined
+make: varparse-dynamic.mk:12: Variable ".TARGXX" is undefined
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varparse-dynamic.mk b/contrib/bmake/unit-tests/varparse-dynamic.mk
index d4d165017a7f..aafe3cca5329 100644
--- a/contrib/bmake/unit-tests/varparse-dynamic.mk
+++ b/contrib/bmake/unit-tests/varparse-dynamic.mk
@@ -1,12 +1,14 @@
-# $NetBSD: varparse-dynamic.mk,v 1.5 2021/02/22 20:38:55 rillig Exp $
+# $NetBSD: varparse-dynamic.mk,v 1.10 2025/01/11 21:21:34 rillig Exp $
# Before 2020-07-27, there was an off-by-one error in Var_Parse that skipped
# the last character in the variable name.
-# To trigger the bug, the variable must not be defined.
+# To trigger the bug, the variable had to be undefined.
.if ${.TARGET} # exact match, may be undefined
.endif
+# expect+1: Variable ".TARGEX" is undefined
.if ${.TARGEX} # 1 character difference, must be defined
.endif
+# expect+1: Variable ".TARGXX" is undefined
.if ${.TARGXX} # 2 characters difference, must be defined
.endif
@@ -22,7 +24,7 @@
.endif
# If a dynamic variable is expanded in a non-local scope, the expression
-# based on this variable is not expanded. But there may be nested variable
+# based on this variable is not expanded. But there may be nested
# expressions in the modifiers, and these are kept unexpanded as well.
.if ${.TARGET:M${:Ufallback}} != "\${.TARGET:M\${:Ufallback}}"
. error
@@ -30,6 +32,3 @@
.if ${.TARGET:M${UNDEF}} != "\${.TARGET:M\${UNDEF}}"
. error
.endif
-
-all:
- @:
diff --git a/contrib/bmake/unit-tests/varparse-errors.exp b/contrib/bmake/unit-tests/varparse-errors.exp
index e47127447cda..2a9be069075f 100644
--- a/contrib/bmake/unit-tests/varparse-errors.exp
+++ b/contrib/bmake/unit-tests/varparse-errors.exp
@@ -1,11 +1,52 @@
-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: varparse-errors.mk:38: Unknown modifier ":Z"
+ while evaluating "${:U:Z}" with value ""
+make: varparse-errors.mk:47: Unknown modifier ":Z"
+ while evaluating "${:U:Z}post" with value ""
+make: varparse-errors.mk:73: Unknown modifier ":OX"
+ while evaluating "${:U:OX:U${IND}} ${:U:OX:U${IND}}" with value ""
+make: varparse-errors.mk:73: Unknown modifier ":OX"
+ while evaluating "${:OX}" with value ""
+ while evaluating variable "IND" with value "${:OX}"
+make: varparse-errors.mk:73: Unknown modifier ":OX"
+ while evaluating "${:U:OX:U${IND}}" with value ""
+make: varparse-errors.mk:73: Unknown modifier ":OX"
+ while evaluating "${:OX}" with value ""
+ while evaluating variable "IND" with value "${:OX}"
+make: varparse-errors.mk:81: Unclosed expression, expecting "}" for modifier "Q"
+ while evaluating "${:U:Q" with value ""
+make: varparse-errors.mk:83: Unclosed expression, expecting "}" for modifier "sh"
+ while evaluating "${:U:sh" with value ""
+make: varparse-errors.mk:85: Unclosed expression, expecting "}" for modifier "tA"
+ while evaluating "${:U:tA" with value ""
+make: varparse-errors.mk:87: Unclosed expression, expecting "}" for modifier "tsX"
+ while evaluating "${:U:tsX" with value ""
+make: varparse-errors.mk:89: Unclosed expression, expecting "}" for modifier "ts"
+ while evaluating "${:U:ts" with value ""
+make: varparse-errors.mk:91: Unclosed expression, expecting "}" for modifier "ts\040"
+ while evaluating "${:U:ts\040" with value ""
+make: varparse-errors.mk:93: Unclosed expression, expecting "}" for modifier "u"
+ while evaluating "${:U:u" with value ""
+make: varparse-errors.mk:95: Unclosed expression, expecting "}" for modifier "H"
+ while evaluating "${:U:H" with value "."
+make: varparse-errors.mk:97: Unclosed expression, expecting "}" for modifier "[1]"
+ while evaluating "${:U:[1]" with value ""
+make: varparse-errors.mk:99: Unclosed expression, expecting "}" for modifier "hash"
+ while evaluating "${:U:hash" with value "b2af338b"
+make: varparse-errors.mk:101: Unclosed expression, expecting "}" for modifier "range"
+ while evaluating "${:U:range" with value "1"
+make: varparse-errors.mk:103: Unclosed expression, expecting "}" for modifier "_"
+ while evaluating "${:U:_" with value ""
+make: varparse-errors.mk:105: Unclosed expression, expecting "}" for modifier "gmtime"
+ while evaluating "${:U:gmtime" with value "<timestamp>"
+make: varparse-errors.mk:107: Unclosed expression, expecting "}" for modifier "localtime"
+ while evaluating "${:U:localtime" with value "<timestamp>"
+make: varparse-errors.tmp:1: Unknown modifier ":Z"
+ while evaluating "${:Z}" with value ""
+ while evaluating variable "INDIRECT" with value "${:Z}"
+ while evaluating variable "VALUE" with value "${INDIRECT}"
+ in varparse-errors.tmp:1
+ in varparse-errors.mk:126
+make: varparse-errors.tmp:1:
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 51a403fa898f..bd74c442e789 100644
--- a/contrib/bmake/unit-tests/varparse-errors.mk
+++ b/contrib/bmake/unit-tests/varparse-errors.mk
@@ -1,12 +1,11 @@
-# $NetBSD: varparse-errors.mk,v 1.5 2022/01/24 22:59:49 rillig Exp $
+# $NetBSD: varparse-errors.mk,v 1.26 2025/06/28 22:39:29 rillig Exp $
-# Tests for parsing and evaluating all kinds of variable expressions.
+# Tests for parsing and evaluating all kinds of expressions.
#
# This is the basis for redesigning the error handling in Var_Parse and
# Var_Subst, collecting typical and not so typical use cases.
#
# See also:
-# VarParseResult
# Var_Parse
# Var_Subst
@@ -18,14 +17,14 @@ INDIRECT= An ${:Uindirect} value.
REF_UNDEF= A reference to an ${UNDEF}undefined variable.
-ERR_UNCLOSED= An ${UNCLOSED variable expression.
+ERR_UNCLOSED= An ${UNCLOSED expression.
ERR_BAD_MOD= An ${:Uindirect:Z} expression with an unknown modifier.
ERR_EVAL= An evaluation error ${:Uvalue:C,.,\3,}.
-# In a conditional, a variable expression that is not enclosed in quotes is
-# expanded using the mode VARE_UNDEFERR.
+# In a conditional, an expression that is not enclosed in quotes is
+# expanded using the mode VARE_EVAL_DEFINED.
# The variable itself must be defined.
# It may refer to undefined variables though.
.if ${REF_UNDEF} != "A reference to an undefined variable."
@@ -35,6 +34,7 @@ ERR_EVAL= An evaluation error ${:Uvalue:C,.,\3,}.
# As of 2020-12-01, errors in the variable name are silently ignored.
# Since var.c 1.754 from 2020-12-20, unknown modifiers at parse time result
# in an error message and a non-zero exit status.
+# expect+1: Unknown modifier ":Z"
VAR.${:U:Z}= unknown modifier in the variable name
.if ${VAR.} != "unknown modifier in the variable name"
. error
@@ -43,6 +43,7 @@ VAR.${:U:Z}= unknown modifier in the variable name
# As of 2020-12-01, errors in the variable name are silently ignored.
# Since var.c 1.754 from 2020-12-20, unknown modifiers at parse time result
# in an error message and a non-zero exit status.
+# expect+1: Unknown modifier ":Z"
VAR.${:U:Z}post= unknown modifier with text in the variable name
.if ${VAR.post} != "unknown modifier with text in the variable name"
. error
@@ -65,7 +66,62 @@ VAR.${:U:Z}post= unknown modifier with text in the variable name
#
#.MAKEFLAGS: -dv
IND= ${:OX}
+# expect+4: Unknown modifier ":OX"
+# expect+3: Unknown modifier ":OX"
+# expect+2: Unknown modifier ":OX"
+# expect+1: Unknown modifier ":OX"
_:= ${:U:OX:U${IND}} ${:U:OX:U${IND}}
#.MAKEFLAGS: -d0
-all:
+
+# Before var.c 1.032 from 2022-08-24, make complained about 'Unknown modifier'
+# or 'Bad modifier' when in fact the modifier was entirely correct, it was
+# just not delimited by either ':' or '}' but instead by '\0'.
+# expect+1: Unclosed expression, expecting "}" for modifier "Q"
+UNCLOSED:= ${:U:Q
+# expect+1: Unclosed expression, expecting "}" for modifier "sh"
+UNCLOSED:= ${:U:sh
+# expect+1: Unclosed expression, expecting "}" for modifier "tA"
+UNCLOSED:= ${:U:tA
+# expect+1: Unclosed expression, expecting "}" for modifier "tsX"
+UNCLOSED:= ${:U:tsX
+# expect+1: Unclosed expression, expecting "}" for modifier "ts"
+UNCLOSED:= ${:U:ts
+# expect+1: Unclosed expression, expecting "}" for modifier "ts\040"
+UNCLOSED:= ${:U:ts\040
+# expect+1: Unclosed expression, expecting "}" for modifier "u"
+UNCLOSED:= ${:U:u
+# expect+1: Unclosed expression, expecting "}" for modifier "H"
+UNCLOSED:= ${:U:H
+# expect+1: Unclosed expression, expecting "}" for modifier "[1]"
+UNCLOSED:= ${:U:[1]
+# expect+1: Unclosed expression, expecting "}" for modifier "hash"
+UNCLOSED:= ${:U:hash
+# expect+1: Unclosed expression, expecting "}" for modifier "range"
+UNCLOSED:= ${:U:range
+# expect+1: Unclosed expression, expecting "}" for modifier "_"
+UNCLOSED:= ${:U:_
+# expect+1: Unclosed expression, expecting "}" for modifier "gmtime"
+UNCLOSED:= ${:U:gmtime
+# expect+1: Unclosed expression, expecting "}" for modifier "localtime"
+UNCLOSED:= ${:U:localtime
+
+
+# In a stack trace that has both evaluation details and included files, list
+# the current file twice: Once in the first line and once in the call
+# hierarchy. While this is redundant, omitting the current file from the
+# call hierarchy is more confusing, as the '.include' line does not contain
+# the faulty expression.
+#
+# expect: make: varparse-errors.tmp:1: Unknown modifier ":Z"
+# expect: while evaluating "${:Z}" with value ""
+# expect: while evaluating variable "INDIRECT" with value "${:Z}"
+# expect: while evaluating variable "VALUE" with value "${INDIRECT}"
+# expect: in varparse-errors.tmp:1
+# expect: in varparse-errors.mk:126
+_!= echo '.info $${VALUE}' > varparse-errors.tmp
+VALUE= ${INDIRECT}
+INDIRECT= ${:Z}
+# The "${.OBJDIR}/" is necessary to bypass the directory cache.
+.include "${.OBJDIR}/varparse-errors.tmp"
+_!= rm -f varparse-errors.tmp
diff --git a/contrib/bmake/unit-tests/varparse-mod.mk b/contrib/bmake/unit-tests/varparse-mod.mk
index 0b4cbf6ca40a..c5fa6f5ece71 100644
--- a/contrib/bmake/unit-tests/varparse-mod.mk
+++ b/contrib/bmake/unit-tests/varparse-mod.mk
@@ -1,6 +1,6 @@
-# $NetBSD: varparse-mod.mk,v 1.1 2020/10/02 20:34:59 rillig Exp $
+# $NetBSD: varparse-mod.mk,v 1.2 2023/11/19 21:47:52 rillig Exp $
-# Tests for parsing variable expressions with modifiers.
+# Tests for parsing expressions with modifiers.
# As of 2020-10-02, the below condition does not result in a parse error.
# The condition contains two separate mistakes. The first mistake is that
@@ -8,7 +8,7 @@
# there is a stray '}' at the end of the whole condition.
#
# As of 2020-10-02, the actual parse result of this condition is a single
-# variable expression with 2 modifiers. The first modifier is
+# expression with 2 modifiers. The first modifier is
# ":!echo "\$VAR"} !". Afterwards, the parser optionally skips a ':' (at the
# bottom of ApplyModifiers) and continues with the next modifier, in this case
# "= "value"", which is interpreted as a SysV substitution modifier with an
diff --git a/contrib/bmake/unit-tests/varparse-undef-partial.mk b/contrib/bmake/unit-tests/varparse-undef-partial.mk
index 27f44d79b31a..9a5704265086 100644
--- a/contrib/bmake/unit-tests/varparse-undef-partial.mk
+++ b/contrib/bmake/unit-tests/varparse-undef-partial.mk
@@ -1,7 +1,7 @@
-# $NetBSD: varparse-undef-partial.mk,v 1.3 2020/11/04 05:10:01 rillig Exp $
+# $NetBSD: varparse-undef-partial.mk,v 1.5 2024/01/07 11:39:04 rillig Exp $
# When an undefined variable is expanded in a ':=' assignment, only the
-# initial '$' of the variable expression is skipped by the parser, while
+# initial '$' of the expression is skipped by the parser, while
# the remaining expression is evaluated. In edge cases this can lead to
# a completely different interpretation of the partially expanded text.
@@ -11,11 +11,10 @@ PARAM= :Q
# The expression ${VAR.${PARAM}} refers to the variable named "VAR.:Q",
# with the ":Q" being part of the name. This variable is not defined,
-# therefore the initial '$' of that whole expression is skipped by the
-# parser (see Var_Subst, the Buf_AddByte in the else branch) and the rest
-# of the expression is expanded as usual.
+# therefore the initial '$' of that whole expression is skipped by the parser
+# (see VarSubstExpr) and the rest of the expression is expanded as usual.
#
-# The resulting variable expression is ${VAR.:Q}, which means that the
+# The resulting expression is ${VAR.:Q}, which means that the
# interpretation of the ":Q" has changed from being part of the variable
# name to being a variable modifier. This is a classical code injection.
EVAL:= ${LIST}
@@ -37,7 +36,7 @@ ${:UVAR.\:Q}= var-dot with parameter :Q
# In contrast to the previous line, evaluating the original LIST again now
# produces a different result since the variable named "VAR.:Q" is now
# defined. It is expanded as usual, interpreting the ":Q" as part of the
-# variable name, as would be expected from reading the variable expression.
+# variable name, as would be expected from reading the expression.
EVAL:= ${LIST}
.if ${EVAL} != "defined var-dot with parameter :Q end"
. error ${EVAL}
diff --git a/contrib/bmake/unit-tests/varquote.exp b/contrib/bmake/unit-tests/varquote.exp
deleted file mode 100644
index 63107bfd34f5..000000000000
--- a/contrib/bmake/unit-tests/varquote.exp
+++ /dev/null
@@ -1,3 +0,0 @@
--fdebug-prefix-map=$NETBSDSRCDIR=/usr/src -fdebug-regex-map=/usr/src/(.*)/obj$=/usr/obj/\1
--fdebug-prefix-map=$NETBSDSRCDIR=/usr/src -fdebug-regex-map=/usr/src/(.*)/obj$=/usr/obj/\1
-exit status 0
diff --git a/contrib/bmake/unit-tests/varquote.mk b/contrib/bmake/unit-tests/varquote.mk
deleted file mode 100644
index 3d5e8a7f32e9..000000000000
--- a/contrib/bmake/unit-tests/varquote.mk
+++ /dev/null
@@ -1,14 +0,0 @@
-# $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'
-all:
- @${MAKE} -f ${MAKEFILE} REPROFLAGS=${REPROFLAGS:S/\$/&&/g:Q}
- @${MAKE} -f ${MAKEFILE} REPROFLAGS=${REPROFLAGS:q}
-.else
-all:
- @printf "%s %s\n" ${REPROFLAGS}
-.endif