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