1*8d5c8e21SSimon J. Gerraty# $NetBSD: varmod-loop.mk,v 1.26 2024/06/02 15:31:26 rillig Exp $ 22c3632d1SSimon J. Gerraty# 38c973ee2SSimon J. Gerraty# Tests for the expression modifier ':@var@body@', which replaces each word of 48c973ee2SSimon J. Gerraty# the expression with the expanded body, which may contain references to the 58c973ee2SSimon J. Gerraty# variable 'var'. For example, '${1 2 3:L:@word@<${word}>@}' encloses each 68c973ee2SSimon J. Gerraty# word in angle quotes, resulting in '<1> <2> <3>'. 78c973ee2SSimon J. Gerraty# 88c973ee2SSimon J. Gerraty# The variable name can be chosen freely, except that it must not contain a 98c973ee2SSimon J. Gerraty# '$'. For simplicity and readability, variable names should only use the 108c973ee2SSimon J. Gerraty# characters 'A-Za-z0-9'. 118c973ee2SSimon J. Gerraty# 128c973ee2SSimon J. Gerraty# The body may contain subexpressions in the form '${...}' or '$(...)'. These 138c973ee2SSimon J. Gerraty# subexpressions differ from everywhere else in makefiles in that the parser 148c973ee2SSimon J. Gerraty# only scans '${...}' for balanced '{' and '}', likewise for '$(...)'. Any 158c973ee2SSimon J. Gerraty# other '$' is left as-is during parsing. Later, when the body is expanded 168c973ee2SSimon J. Gerraty# for each word, each '$$' is interpreted as a single '$', and the remaining 178c973ee2SSimon J. Gerraty# '$' are interpreted as expressions, like when evaluating a regular variable. 182c3632d1SSimon J. Gerraty 1912904384SSimon J. Gerraty# Force the test results to be independent of the default value of this 2012904384SSimon J. Gerraty# setting, which is 'yes' for NetBSD's usr.bin/make but 'no' for the bmake 2112904384SSimon J. Gerraty# distribution and pkgsrc/devel/bmake. 22e2eeea75SSimon J. Gerraty.MAKE.SAVE_DOLLARS= yes 23e2eeea75SSimon J. Gerraty 24b0c40a00SSimon J. Gerratyall: varname-overwriting-target 252c3632d1SSimon J. Gerratyall: mod-loop-dollar 262c3632d1SSimon J. Gerraty 27b0c40a00SSimon J. Gerratyvarname-overwriting-target: 282c3632d1SSimon J. Gerraty # Even "@" works as a variable name since the variable is installed 292c3632d1SSimon J. Gerraty # in the "current" scope, which in this case is the one from the 30b0c40a00SSimon J. Gerraty # target. Because of this, after the loop has finished, '$@' is 31b0c40a00SSimon J. Gerraty # undefined. This is something that make doesn't expect, this may 32b0c40a00SSimon J. Gerraty # even trigger an assertion failure somewhere. 332c3632d1SSimon J. Gerraty @echo :$@: :${:U1 2 3:@\@@x${@}y@}: :$@: 34956e45f6SSimon J. Gerraty 352c3632d1SSimon J. Gerraty 36e2eeea75SSimon J. Gerraty# Demonstrate that it is possible to generate dollar signs using the 372c3632d1SSimon J. Gerraty# :@ modifier. 382c3632d1SSimon J. Gerraty# 392c3632d1SSimon J. Gerraty# These are edge cases that could have resulted in a parse error as well 402c3632d1SSimon J. Gerraty# since the $@ at the end could have been interpreted as a variable, which 412c3632d1SSimon J. Gerraty# would mean a missing closing @ delimiter. 422c3632d1SSimon J. Gerratymod-loop-dollar: 432c3632d1SSimon J. Gerraty @echo $@:${:U1:@word@${word}$@:Q}: 442c3632d1SSimon J. Gerraty @echo $@:${:U2:@word@$${word}$$@:Q}: 452c3632d1SSimon J. Gerraty @echo $@:${:U3:@word@$$${word}$$$@:Q}: 462c3632d1SSimon J. Gerraty @echo $@:${:U4:@word@$$$${word}$$$$@:Q}: 472c3632d1SSimon J. Gerraty @echo $@:${:U5:@word@$$$$${word}$$$$$@:Q}: 482c3632d1SSimon J. Gerraty @echo $@:${:U6:@word@$$$$$${word}$$$$$$@:Q}: 49956e45f6SSimon J. Gerraty 50956e45f6SSimon J. Gerraty# It may happen that there are nested :@ modifiers that use the same name for 51956e45f6SSimon J. Gerraty# for the loop variable. These modifiers influence each other. 52956e45f6SSimon J. Gerraty# 53956e45f6SSimon J. Gerraty# As of 2020-10-18, the :@ modifier is implemented by actually setting a 54dba7b0efSSimon J. Gerraty# variable in the scope of the expression and deleting it again after the 55d5e0a182SSimon J. Gerraty# loop. This is different from the .for loops, which substitute the 56956e45f6SSimon J. Gerraty# expression with ${:Uvalue}, leading to different unwanted side effects. 57956e45f6SSimon J. Gerraty# 58956e45f6SSimon J. Gerraty# To make the behavior more predictable, the :@ modifier should restore the 59956e45f6SSimon J. Gerraty# loop variable to the value it had before the loop. This would result in 60956e45f6SSimon J. Gerraty# the string "1a b c1 2a b c2 3a b c3", making the two loops independent. 61956e45f6SSimon J. Gerraty.if ${:U1 2 3:@i@$i${:Ua b c:@i@$i@}${i:Uu}@} != "1a b cu 2a b cu 3a b cu" 62956e45f6SSimon J. Gerraty. error 63956e45f6SSimon J. Gerraty.endif 64956e45f6SSimon J. Gerraty 65956e45f6SSimon J. Gerraty# During the loop, the variable is actually defined and nonempty. 66956e45f6SSimon J. Gerraty# If the loop were implemented in the same way as the .for loop, the variable 67956e45f6SSimon J. Gerraty# would be neither defined nor nonempty since all expressions of the form 68956e45f6SSimon J. Gerraty# ${var} would have been replaced with ${:Uword} before evaluating them. 69956e45f6SSimon J. Gerraty.if defined(var) 70956e45f6SSimon J. Gerraty. error 71956e45f6SSimon J. Gerraty.endif 72956e45f6SSimon J. Gerraty.if ${:Uword:@var@${defined(var):?def:undef} ${empty(var):?empty:nonempty}@} \ 73956e45f6SSimon J. Gerraty != "def nonempty" 74956e45f6SSimon J. Gerraty. error 75956e45f6SSimon J. Gerraty.endif 76956e45f6SSimon J. Gerraty.if defined(var) 77956e45f6SSimon J. Gerraty. error 78956e45f6SSimon J. Gerraty.endif 79e2eeea75SSimon J. Gerraty 80e2eeea75SSimon J. Gerraty# Assignment using the ':=' operator, combined with the :@var@ modifier 81e2eeea75SSimon J. Gerraty# 82e2eeea75SSimon J. Gerraty8_DOLLARS= $$$$$$$$ 83e2eeea75SSimon J. Gerraty# This string literal is written with 8 dollars, and this is saved as the 84e2eeea75SSimon J. Gerraty# variable value. But as soon as this value is evaluated, it goes through 85*8d5c8e21SSimon J. Gerraty# Var_Subst, which replaces each '$$' with a single '$'. 86*8d5c8e21SSimon J. Gerraty# See ApplyModifier_Loop and ParseModifierPart for examples. 87e2eeea75SSimon J. Gerraty# 88e2eeea75SSimon J. Gerraty.MAKEFLAGS: -dcp 89e2eeea75SSimon J. GerratyUSE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$ 90e2eeea75SSimon J. Gerraty.if ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$" 91e2eeea75SSimon J. Gerraty. error 92e2eeea75SSimon J. Gerraty.endif 93e2eeea75SSimon J. Gerraty# 94e2eeea75SSimon J. GerratySUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS} 95b0c40a00SSimon J. Gerraty# The ':=' assignment operator evaluates the variable value using the mode 96*8d5c8e21SSimon J. Gerraty# VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED, which means that some dollar signs are 97*8d5c8e21SSimon J. Gerraty# preserved, but not all. The dollar signs in the top-level expression and in 98*8d5c8e21SSimon J. Gerraty# the indirect ${8_DOLLARS} are preserved. 99e2eeea75SSimon J. Gerraty# 100*8d5c8e21SSimon J. Gerraty# The modifier :@var@ does not preserve the dollar signs though, no 101e2eeea75SSimon J. Gerraty# matter in which context it is evaluated. What happens in detail is: 102e2eeea75SSimon J. Gerraty# First, the modifier part "${8_DOLLARS}" is parsed without expanding it. 103e2eeea75SSimon J. Gerraty# Next, each word of the value is expanded on its own, and at this moment 104b0c40a00SSimon J. Gerraty# in ApplyModifier_Loop, the flag keepDollar is not passed down to 105e2eeea75SSimon J. Gerraty# ModifyWords, resulting in "$$$$" for the first word of USE_8_DOLLARS. 106e2eeea75SSimon J. Gerraty# 107e2eeea75SSimon J. Gerraty# The remaining words of USE_8_DOLLARS are not affected by any variable 108b0c40a00SSimon J. Gerraty# modifier and are thus expanded with the flag keepDollar in action. 109e2eeea75SSimon J. Gerraty# The variable SUBST_CONTAINING_LOOP therefore gets assigned the raw value 110e2eeea75SSimon J. Gerraty# "$$$$ $$$$$$$$ $$$$$$$$". 111e2eeea75SSimon J. Gerraty# 112d5e0a182SSimon J. Gerraty# The expression in the condition then expands this raw stored value 113e2eeea75SSimon J. Gerraty# once, resulting in "$$ $$$$ $$$$". The effects from VARE_KEEP_DOLLAR no 114e2eeea75SSimon J. Gerraty# longer take place since they had only been active during the evaluation of 115e2eeea75SSimon J. Gerraty# the variable assignment. 116e2eeea75SSimon J. Gerraty.if ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$" 117e2eeea75SSimon J. Gerraty. error 118e2eeea75SSimon J. Gerraty.endif 119e2eeea75SSimon J. Gerraty.MAKEFLAGS: -d0 120b0c40a00SSimon J. Gerraty 121b0c40a00SSimon J. Gerraty# After looping over the words of the expression, the loop variable gets 122b0c40a00SSimon J. Gerraty# undefined. The modifier ':@' uses an ordinary global variable for this, 123b0c40a00SSimon J. Gerraty# which is different from the '.for' loop, which replaces ${var} with 124b0c40a00SSimon J. Gerraty# ${:Uvalue} in the body of the loop. This choice of implementation detail 125b0c40a00SSimon J. Gerraty# can be used for a nasty side effect. The expression ${:U:@VAR@@} evaluates 126b0c40a00SSimon J. Gerraty# to an empty string, plus it undefines the variable 'VAR'. This is the only 127b0c40a00SSimon J. Gerraty# possibility to undefine a global variable during evaluation. 128b0c40a00SSimon J. GerratyGLOBAL= before-global 129b0c40a00SSimon J. GerratyRESULT:= ${:U${GLOBAL} ${:U:@GLOBAL@@} ${GLOBAL:Uundefined}} 130b0c40a00SSimon J. Gerraty.if ${RESULT} != "before-global undefined" 131b0c40a00SSimon J. Gerraty. error 132b0c40a00SSimon J. Gerraty.endif 133b0c40a00SSimon J. Gerraty 134b0c40a00SSimon J. Gerraty# The above side effect of undefining a variable from a certain scope can be 135b0c40a00SSimon J. Gerraty# further combined with the otherwise undocumented implementation detail that 136b0c40a00SSimon J. Gerraty# the argument of an '.if' directive is evaluated in cmdline scope. Putting 137b0c40a00SSimon J. Gerraty# these together makes it possible to undefine variables from the cmdline 138b0c40a00SSimon J. Gerraty# scope, something that is not possible in a straight-forward way. 139b0c40a00SSimon J. Gerraty.MAKEFLAGS: CMDLINE=cmdline 140b0c40a00SSimon J. Gerraty.if ${:U${CMDLINE}${:U:@CMDLINE@@}} != "cmdline" 141b0c40a00SSimon J. Gerraty. error 142b0c40a00SSimon J. Gerraty.endif 143b0c40a00SSimon J. Gerraty# Now the cmdline variable got undefined. 144b0c40a00SSimon J. Gerraty.if ${CMDLINE} != "cmdline" 145b0c40a00SSimon J. Gerraty. error 146b0c40a00SSimon J. Gerraty.endif 147b0c40a00SSimon J. Gerraty# At this point, it still looks as if the cmdline variable were defined, 148b0c40a00SSimon J. Gerraty# since the value of CMDLINE is still "cmdline". That impression is only 149b0c40a00SSimon J. Gerraty# superficial though, the cmdline variable is actually deleted. To 150b0c40a00SSimon J. Gerraty# demonstrate this, it is now possible to override its value using a global 151b0c40a00SSimon J. Gerraty# variable, something that was not possible before: 152b0c40a00SSimon J. GerratyCMDLINE= global 153b0c40a00SSimon J. Gerraty.if ${CMDLINE} != "global" 154b0c40a00SSimon J. Gerraty. error 155b0c40a00SSimon J. Gerraty.endif 156b0c40a00SSimon J. Gerraty# Now undefine that global variable again, to get back to the original value. 157b0c40a00SSimon J. Gerraty.undef CMDLINE 158b0c40a00SSimon J. Gerraty.if ${CMDLINE} != "cmdline" 159b0c40a00SSimon J. Gerraty. error 160b0c40a00SSimon J. Gerraty.endif 161b0c40a00SSimon J. Gerraty# What actually happened is that when CMDLINE was set by the '.MAKEFLAGS' 162b0c40a00SSimon J. Gerraty# target in the cmdline scope, that same variable was exported to the 163b0c40a00SSimon J. Gerraty# environment, see Var_SetWithFlags. 164b0c40a00SSimon J. Gerraty.unexport CMDLINE 165b0c40a00SSimon J. Gerraty.if ${CMDLINE} != "cmdline" 166b0c40a00SSimon J. Gerraty. error 167b0c40a00SSimon J. Gerraty.endif 168b0c40a00SSimon J. Gerraty# The above '.unexport' has no effect since UnexportVar requires a global 169b0c40a00SSimon J. Gerraty# variable of the same name to be defined, otherwise nothing is unexported. 170b0c40a00SSimon J. GerratyCMDLINE= global 171b0c40a00SSimon J. Gerraty.unexport CMDLINE 172b0c40a00SSimon J. Gerraty.undef CMDLINE 173b0c40a00SSimon J. Gerraty.if ${CMDLINE} != "cmdline" 174b0c40a00SSimon J. Gerraty. error 175b0c40a00SSimon J. Gerraty.endif 176b0c40a00SSimon J. Gerraty# This still didn't work since there must not only be a global variable, the 177b0c40a00SSimon J. Gerraty# variable must be marked as exported as well, which it wasn't before. 178b0c40a00SSimon J. GerratyCMDLINE= global 179b0c40a00SSimon J. Gerraty.export CMDLINE 180b0c40a00SSimon J. Gerraty.unexport CMDLINE 181b0c40a00SSimon J. Gerraty.undef CMDLINE 182b0c40a00SSimon J. Gerraty.if ${CMDLINE:Uundefined} != "undefined" 183b0c40a00SSimon J. Gerraty. error 184b0c40a00SSimon J. Gerraty.endif 185b0c40a00SSimon J. Gerraty# Finally the variable 'CMDLINE' from the cmdline scope is gone, and all its 186b0c40a00SSimon J. Gerraty# traces from the environment are gone as well. To do that, a global variable 187b0c40a00SSimon J. Gerraty# had to be defined and exported, something that is far from obvious. To 188b0c40a00SSimon J. Gerraty# recap, here is the essence of the above story: 189b0c40a00SSimon J. Gerraty.MAKEFLAGS: CMDLINE=cmdline # have a cmdline + environment variable 190b0c40a00SSimon J. Gerraty.if ${:U:@CMDLINE@@}} # undefine cmdline, keep environment 191b0c40a00SSimon J. Gerraty.endif 192b0c40a00SSimon J. GerratyCMDLINE= global # needed for deleting the environment 193b0c40a00SSimon J. Gerraty.export CMDLINE # needed for deleting the environment 194b0c40a00SSimon J. Gerraty.unexport CMDLINE # delete the environment 195b0c40a00SSimon J. Gerraty.undef CMDLINE # delete the global helper variable 196b0c40a00SSimon J. Gerraty.if ${CMDLINE:Uundefined} != "undefined" 197b0c40a00SSimon J. Gerraty. error # 'CMDLINE' is gone now from all scopes 198b0c40a00SSimon J. Gerraty.endif 199b0c40a00SSimon J. Gerraty 2004fde40d9SSimon J. Gerraty 2014fde40d9SSimon J. Gerraty# In the loop body text of the ':@' modifier, a literal '$' is written as '$$', 2024fde40d9SSimon J. Gerraty# not '\$'. In the following example, each '$$' turns into a single '$', 2034fde40d9SSimon J. Gerraty# except for '$i', which is replaced with the then-current value '1' of the 2044fde40d9SSimon J. Gerraty# iteration variable. 2054fde40d9SSimon J. Gerraty# 2068c973ee2SSimon J. Gerraty# See parse-var.mk, keyword 'BRACE_GROUP'. 2074fde40d9SSimon J. Gerratyall: varmod-loop-literal-dollar 2084fde40d9SSimon J. Gerratyvarmod-loop-literal-dollar: .PHONY 2094fde40d9SSimon J. Gerraty : ${:U1:@i@ t=$$(( $${t:-0} + $i ))@} 2104fde40d9SSimon J. Gerraty 2114fde40d9SSimon J. Gerraty 2124fde40d9SSimon J. Gerraty# When parsing the loop body, each '\$', '\@' and '\\' is unescaped to '$', 2138c973ee2SSimon J. Gerraty# '@' and '\', respectively; all other backslashes are retained. 2144fde40d9SSimon J. Gerraty# 2154fde40d9SSimon J. Gerraty# In practice, the '$' is not escaped as '\$', as there is a second round of 2164fde40d9SSimon J. Gerraty# unescaping '$$' to '$' later when the loop body is expanded after setting the 2174fde40d9SSimon J. Gerraty# iteration variable. 2184fde40d9SSimon J. Gerraty# 2194fde40d9SSimon J. Gerraty# After the iteration variable has been set, the loop body is expanded with 2204fde40d9SSimon J. Gerraty# this unescaping, regardless of whether .MAKE.SAVE_DOLLARS is set or not: 2214fde40d9SSimon J. Gerraty# $$ a literal '$' 2224fde40d9SSimon J. Gerraty# $x, ${var}, $(var) a nested expression 2234fde40d9SSimon J. Gerraty# any other character itself 2244fde40d9SSimon J. Gerratyall: escape-modifier 2254fde40d9SSimon J. Gerratyescape-modifier: .PHONY 2264fde40d9SSimon J. Gerraty # In the first round, '\$ ' is unescaped to '$ ', and since the 2274fde40d9SSimon J. Gerraty # variable named ' ' is not defined, the expression '$ ' expands to an 2284fde40d9SSimon J. Gerraty # empty string. 2294fde40d9SSimon J. Gerraty # expect: : dollar=end 2304fde40d9SSimon J. Gerraty : ${:U1:@i@ dollar=\$ end@} 2314fde40d9SSimon J. Gerraty 2324fde40d9SSimon J. Gerraty # Like in other modifiers, '\ ' is preserved, since ' ' is not one of 2334fde40d9SSimon J. Gerraty # the characters that _must_ be escaped. 2344fde40d9SSimon J. Gerraty # expect: : backslash=\ end 2354fde40d9SSimon J. Gerraty : ${:U1:@i@ backslash=\ end@} 2364fde40d9SSimon J. Gerraty 2374fde40d9SSimon J. Gerraty # expect: : dollar=$ at=@ backslash=\ end 2384fde40d9SSimon J. Gerraty : ${:U1:@i@ dollar=\$\$ at=\@ backslash=\\ end@} 2394fde40d9SSimon J. Gerraty # expect: : dollar=$$ at=@@ backslash=\\ end 2404fde40d9SSimon J. Gerraty : ${:U1:@i@ dollar=\$\$\$\$ at=\@\@ backslash=\\\\ end@} 2414fde40d9SSimon J. Gerraty # expect: : dollar=$$ at=@@ backslash=\\ end 2424fde40d9SSimon J. Gerraty : ${:U1:@i@ dollar=$$$$ at=\@\@ backslash=\\\\ end@} 2434fde40d9SSimon J. Gerraty 24412904384SSimon J. Gerratyall: .PHONY 245