1# $NetBSD: var-op-expand.mk,v 1.20 2024/04/20 10:18:55 rillig Exp $ 2# 3# Tests for the := variable assignment operator, which expands its 4# right-hand side. 5# 6# See also: 7# varname-dot-make-save_dollars.mk 8 9# Force the test results to be independent of the default value of this 10# setting, which is 'yes' for NetBSD's usr.bin/make but 'no' for the bmake 11# distribution and pkgsrc/devel/bmake. 12.MAKE.SAVE_DOLLARS:= yes 13 14# If the right-hand side does not contain a dollar sign, the ':=' assignment 15# operator has the same effect as the '=' assignment operator. 16VAR:= value 17.if ${VAR} != "value" 18. error 19.endif 20 21# When a ':=' assignment is performed, its right-hand side is evaluated and 22# expanded as far as possible. Contrary to other situations, '$$' and 23# expressions based on undefined variables are preserved though. 24# 25# Whether an expression is undefined or not is determined at the end 26# of evaluating the expression. The consequence is that ${:Ufallback} expands 27# to "fallback"; initially this expression is undefined since it is based on 28# the variable named "", which is guaranteed to be never defined, but at the 29# end of evaluating the expression ${:Ufallback}, the modifier ':U' has turned 30# the expression into a defined expression. 31 32 33# literal dollar signs 34VAR:= $$ $$$$ $$$$$$$$ 35.if ${VAR} != "\$ \$\$ \$\$\$\$" 36. error 37.endif 38 39 40# reference to a variable containing literal dollar signs 41REF= $$ $$$$ $$$$$$$$ 42VAR:= ${REF} 43REF= too late 44.if ${VAR} != "\$ \$\$ \$\$\$\$" 45. error 46.endif 47 48 49# reference to an undefined variable 50.undef UNDEF 51VAR:= <${UNDEF}> 52.if ${VAR} != "<>" 53. error 54.endif 55UNDEF= after 56.if ${VAR} != "<after>" 57. error 58.endif 59 60 61# reference to a variable whose name is computed from another variable 62REF2= referred to 63REF= REF2 64VAR:= ${${REF}} 65REF= too late 66.if ${VAR} != "referred to" 67. error 68.endif 69 70 71# expression with an indirect modifier referring to an undefined variable 72.undef UNDEF 73VAR:= ${:${UNDEF}} 74.if ${VAR} != "" 75. error 76.endif 77UNDEF= Uwas undefined 78.if ${VAR} != "was undefined" 79. error 80.endif 81 82 83# expression with an indirect modifier referring to another variable that 84# in turn refers to an undefined variable 85# 86# XXX: Even though this is a ':=' assignment, the '${UNDEF}' in the part of 87# the variable modifier is not preserved. To preserve it, ParseModifierPart 88# would have to call VarSubstExpr somehow since this is the only piece of 89# code that takes care of this global variable. 90.undef UNDEF 91REF= U${UNDEF} 92#.MAKEFLAGS: -dv 93VAR:= ${:${REF}} 94#.MAKEFLAGS: -d0 95REF= too late 96UNDEF= Uwas undefined 97.if ${VAR} != "" 98. error 99.endif 100 101 102# In variable assignments using the ':=' operator, undefined variables are 103# preserved, no matter how indirectly they are referenced. 104.undef REF3 105REF2= <${REF3}> 106REF= ${REF2} 107VAR:= ${REF} 108.if ${VAR} != "<>" 109. error 110.endif 111REF3= too late 112.if ${VAR} != "<too late>" 113. error 114.endif 115 116 117# In variable assignments using the ':=' operator, '$$' are preserved, no 118# matter how indirectly they are referenced. 119REF2= REF2:$$ $$$$ 120REF= REF:$$ $$$$ ${REF2} 121VAR:= VAR:$$ $$$$ ${REF} 122.if ${VAR} != "VAR:\$ \$\$ REF:\$ \$\$ REF2:\$ \$\$" 123. error 124.endif 125 126 127# In variable assignments using the ':=' operator, '$$' are preserved in the 128# expressions of the top level, but not in expressions that are nested. 129VAR:= top:$$ ${:Unest1\:\$\$} ${:Unest2${:U\:\$\$}} 130.if ${VAR} != "top:\$ nest1:\$ nest2:\$" 131. error 132.endif 133 134 135# In variable assignments using the ':=' operator, there may be expressions 136# containing variable modifiers, and these modifiers may refer to other 137# variables. These referred-to variables are expanded at the time of 138# assignment. The undefined variables are kept as-is and are later expanded 139# when evaluating the condition. 140# 141# Contrary to the assignment operator '=', the assignment operator ':=' 142# consumes the '$' from modifier parts. 143REF.word= 1:$$ 2:$$$$ 4:$$$$$$$$ 144.undef REF.undef 145VAR:= ${:Uword undef:@word@${REF.${word}}@}, direct: ${REF.word} ${REF.undef} 146REF.word= word.after 147REF.undef= undef.after 148.if ${VAR} != "1:2:\$ 4:\$\$ undef.after, direct: 1:\$ 2:\$\$ 4:\$\$\$\$ undef.after" 149. error 150.endif 151 152# Just for comparison, the previous example using the assignment operator '=' 153# instead of ':='. The right-hand side of the assignment is not evaluated at 154# the time of assignment but only later, when ${VAR} appears in the condition. 155# 156# At that point, both REF.word and REF.undef are defined. 157REF.word= 1:$$ 2:$$$$ 4:$$$$$$$$ 158.undef REF.undef 159VAR= ${:Uword undef:@word@${REF.${word}}@}, direct: ${REF.word} ${REF.undef} 160REF.word= word.after 161REF.undef= undef.after 162.if ${VAR} != "word.after undef.after, direct: word.after undef.after" 163. error 164.endif 165 166 167# Between var.c 1.42 from 2000-05-11 and before parse.c 1.520 from 2020-12-27, 168# if the variable name in a ':=' assignment referred to an undefined variable, 169# there were actually 2 assignments to different variables: 170# 171# Global["VAR_SUBST_${UNDEF}"] = "" 172# Global["VAR_SUBST_"] = "" 173# 174# The variable name with the empty value actually included a dollar sign. 175# Variable names with dollars are not used in practice. 176# 177# It might be a good idea to forbid undefined variables on the left-hand side 178# of a variable assignment. 179.undef UNDEF 180VAR_ASSIGN_${UNDEF}= assigned by '=' 181VAR_SUBST_${UNDEF}:= assigned by ':=' 182.if ${VAR_ASSIGN_} != "assigned by '='" 183. error 184.endif 185.if defined(${:UVAR_SUBST_\${UNDEF\}}) 186. error 187.endif 188.if ${VAR_SUBST_} != "assigned by ':='" 189. error 190.endif 191 192 193# The following test case demonstrates that the variable 'LATER' is preserved 194# in the ':=' assignment since the variable 'LATER' is not yet defined. 195# After the assignment to 'LATER', evaluating the variable 'INDIRECT' 196# evaluates 'LATER' as well. 197# 198.undef LATER 199INDIRECT:= ${LATER:S,value,replaced,} 200.if ${INDIRECT} != "" 201. error 202.endif 203LATER= late-value 204.if ${INDIRECT} != "late-replaced" 205. error 206.endif 207 208 209# Same as the test case above, except for the additional modifier ':tl' when 210# evaluating the variable 'INDIRECT'. Nothing surprising here. 211.undef LATER 212.undef later 213INDIRECT:= ${LATER:S,value,replaced,} 214.if ${INDIRECT:tl} != "" 215. error 216.endif 217LATER= uppercase-value 218later= lowercase-value 219.if ${INDIRECT:tl} != "uppercase-replaced" 220. error 221.endif 222 223 224# Similar to the two test cases above, the situation gets a bit more involved 225# here, due to the double indirection. The variable 'indirect' is supposed to 226# be the lowercase version of the variable 'INDIRECT'. 227# 228# The assignment operator ':=' for the variable 'INDIRECT' could be a '=' as 229# well, it wouldn't make a difference in this case. The crucial detail is the 230# assignment operator ':=' for the variable 'indirect'. During this 231# assignment, the variable modifier ':S,value,replaced,' is converted to 232# lowercase, which turns 'S' into 's', thus producing an unknown modifier. 233# In this case, make issues a warning, but in cases where the modifier 234# includes a '=', the modifier would be interpreted as a SysV-style 235# substitution like '.c=.o', and make would not issue a warning, leading to 236# silent unexpected behavior. 237# 238# As of 2021-11-20, the actual behavior is unexpected. Fixing it is not 239# trivial. When the assignment to 'indirect' takes place, the expressions 240# from the nested expression could be preserved, like this: 241# 242# Start with: 243# 244# indirect:= ${INDIRECT:tl} 245# 246# Since INDIRECT is defined, expand it, remembering that the modifier 247# ':tl' must still be applied to the final result. 248# 249# indirect:= ${LATER:S,value,replaced,} \ 250# OK \ 251# ${LATER:value=sysv} 252# 253# The variable 'LATER' is not defined. An idea may be to append the 254# remaining modifier ':tl' to each expression that is starting with an 255# undefined variable, resulting in: 256# 257# indirect:= ${LATER:S,value,replaced,:tl} \ 258# OK \ 259# ${LATER:value=sysv:tl} 260# 261# This would work for the first expression. The second expression ends 262# with the SysV modifier ':from=to', and when this modifier is parsed, 263# it consumes all characters until the end of the expression, which in 264# this case would replace the suffix 'value' with the literal 'sysv:tl', 265# ignoring that the ':tl' was intended to be an additional modifier. 266# 267# Due to all of this, this surprising behavior is not easy to fix. 268# 269.undef LATER 270.undef later 271INDIRECT:= ${LATER:S,value,replaced,} OK ${LATER:value=sysv} 272indirect:= ${INDIRECT:tl} 273# expect+1: while evaluating variable "indirect": while evaluating variable "later": Unknown modifier "s,value,replaced," 274.if ${indirect} != " ok " 275. error 276.else 277# expect+1: warning: XXX Neither branch should be taken. 278. warning XXX Neither branch should be taken. 279.endif 280LATER= uppercase-value 281later= lowercase-value 282# expect+1: while evaluating variable "indirect": while evaluating variable "later": Unknown modifier "s,value,replaced," 283.if ${indirect} != "uppercase-replaced ok uppercase-sysv" 284# expect+1: warning: XXX Neither branch should be taken. 285. warning XXX Neither branch should be taken. 286.else 287. error 288.endif 289 290 291all: 292 @:; 293