diff options
Diffstat (limited to 'contrib/bmake/unit-tests')
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 |