aboutsummaryrefslogtreecommitdiff
path: root/contrib/bmake/unit-tests/var-op-expand.mk
blob: fb1e6d2c390f2bc8d89382a1cafb13bcfe82ca09 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# $NetBSD: var-op-expand.mk,v 1.20 2024/04/20 10:18:55 rillig Exp $
#
# Tests for the := variable assignment operator, which expands its
# right-hand side.
#
# See also:
#	varname-dot-make-save_dollars.mk

# 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
# distribution and pkgsrc/devel/bmake.
.MAKE.SAVE_DOLLARS:=	yes

# If the right-hand side does not contain a dollar sign, the ':=' assignment
# operator has the same effect as the '=' assignment operator.
VAR:=			value
.if ${VAR} != "value"
.  error
.endif

# When a ':=' assignment is performed, its right-hand side is evaluated and
# expanded as far as possible.  Contrary to other situations, '$$' and
# expressions based on undefined variables are preserved though.
#
# 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
# end of evaluating the expression ${:Ufallback}, the modifier ':U' has turned
# the expression into a defined expression.


# literal dollar signs
VAR:=		$$ $$$$ $$$$$$$$
.if ${VAR} != "\$ \$\$ \$\$\$\$"
.  error
.endif


# reference to a variable containing literal dollar signs
REF=		$$ $$$$ $$$$$$$$
VAR:=		${REF}
REF=		too late
.if ${VAR} != "\$ \$\$ \$\$\$\$"
.  error
.endif


# reference to an undefined variable
.undef UNDEF
VAR:=		<${UNDEF}>
.if ${VAR} != "<>"
.  error
.endif
UNDEF=		after
.if ${VAR} != "<after>"
.  error
.endif


# reference to a variable whose name is computed from another variable
REF2=		referred to
REF=		REF2
VAR:=		${${REF}}
REF=		too late
.if ${VAR} != "referred to"
.  error
.endif


# 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
.endif


# expression with an indirect modifier referring to another variable that
# in turn refers to an undefined variable
#
# XXX: Even though this is a ':=' assignment, the '${UNDEF}' in the part of
# the variable modifier is not preserved.  To preserve it, ParseModifierPart
# would have to call VarSubstExpr somehow since this is the only piece of
# code that takes care of this global variable.
.undef UNDEF
REF=		U${UNDEF}
#.MAKEFLAGS: -dv
VAR:=		${:${REF}}
#.MAKEFLAGS: -d0
REF=		too late
UNDEF=		Uwas undefined
.if ${VAR} != ""
.  error
.endif


# In variable assignments using the ':=' operator, undefined variables are
# preserved, no matter how indirectly they are referenced.
.undef REF3
REF2=		<${REF3}>
REF=		${REF2}
VAR:=		${REF}
.if ${VAR} != "<>"
.  error
.endif
REF3=		too late
.if ${VAR} != "<too late>"
.  error
.endif


# In variable assignments using the ':=' operator, '$$' are preserved, no
# matter how indirectly they are referenced.
REF2=		REF2:$$ $$$$
REF=		REF:$$ $$$$ ${REF2}
VAR:=		VAR:$$ $$$$ ${REF}
.if ${VAR} != "VAR:\$ \$\$ REF:\$ \$\$ REF2:\$ \$\$"
.  error
.endif


# In variable assignments using the ':=' operator, '$$' are preserved in the
# expressions of the top level, but not in expressions that are nested.
VAR:=		top:$$ ${:Unest1\:\$\$} ${:Unest2${:U\:\$\$}}
.if ${VAR} != "top:\$ nest1:\$ nest2:\$"
.  error
.endif


# In variable assignments using the ':=' operator, there may be expressions
# containing variable modifiers, and these modifiers may refer to other
# variables.  These referred-to variables are expanded at the time of
# assignment.  The undefined variables are kept as-is and are later expanded
# when evaluating the condition.
#
# Contrary to the assignment operator '=', the assignment operator ':='
# consumes the '$' from modifier parts.
REF.word=	1:$$ 2:$$$$ 4:$$$$$$$$
.undef REF.undef
VAR:=		${:Uword undef:@word@${REF.${word}}@}, direct: ${REF.word} ${REF.undef}
REF.word=	word.after
REF.undef=	undef.after
.if ${VAR} != "1:2:\$ 4:\$\$ undef.after, direct: 1:\$ 2:\$\$ 4:\$\$\$\$ undef.after"
.  error
.endif

# Just for comparison, the previous example using the assignment operator '='
# instead of ':='.  The right-hand side of the assignment is not evaluated at
# the time of assignment but only later, when ${VAR} appears in the condition.
#
# At that point, both REF.word and REF.undef are defined.
REF.word=	1:$$ 2:$$$$ 4:$$$$$$$$
.undef REF.undef
VAR=		${:Uword undef:@word@${REF.${word}}@}, direct: ${REF.word} ${REF.undef}
REF.word=	word.after
REF.undef=	undef.after
.if ${VAR} != "word.after undef.after, direct: word.after undef.after"
.  error
.endif


# Between var.c 1.42 from 2000-05-11 and before parse.c 1.520 from 2020-12-27,
# if the variable name in a ':=' assignment referred to an undefined variable,
# there were actually 2 assignments to different variables:
#
#	Global["VAR_SUBST_${UNDEF}"] = ""
#	Global["VAR_SUBST_"] = ""
#
# The variable name with the empty value actually included a dollar sign.
# Variable names with dollars are not used in practice.
#
# It might be a good idea to forbid undefined variables on the left-hand side
# of a variable assignment.
.undef UNDEF
VAR_ASSIGN_${UNDEF}=	assigned by '='
VAR_SUBST_${UNDEF}:=	assigned by ':='
.if ${VAR_ASSIGN_} != "assigned by '='"
.  error
.endif
.if defined(${:UVAR_SUBST_\${UNDEF\}})
.  error
.endif
.if ${VAR_SUBST_} != "assigned by ':='"
.  error
.endif


# The following test case demonstrates that the variable 'LATER' is preserved
# in the ':=' assignment since the variable 'LATER' is not yet defined.
# After the assignment to 'LATER', evaluating the variable 'INDIRECT'
# evaluates 'LATER' as well.
#
.undef LATER
INDIRECT:=	${LATER:S,value,replaced,}
.if ${INDIRECT} != ""
.  error
.endif
LATER=	late-value
.if ${INDIRECT} != "late-replaced"
.  error
.endif


# Same as the test case above, except for the additional modifier ':tl' when
# evaluating the variable 'INDIRECT'.  Nothing surprising here.
.undef LATER
.undef later
INDIRECT:=	${LATER:S,value,replaced,}
.if ${INDIRECT:tl} != ""
.  error
.endif
LATER=	uppercase-value
later=	lowercase-value
.if ${INDIRECT:tl} != "uppercase-replaced"
.  error
.endif


# Similar to the two test cases above, the situation gets a bit more involved
# here, due to the double indirection.  The variable 'indirect' is supposed to
# be the lowercase version of the variable 'INDIRECT'.
#
# The assignment operator ':=' for the variable 'INDIRECT' could be a '=' as
# well, it wouldn't make a difference in this case.  The crucial detail is the
# assignment operator ':=' for the variable 'indirect'.  During this
# assignment, the variable modifier ':S,value,replaced,' is converted to
# lowercase, which turns 'S' into 's', thus producing an unknown modifier.
# In this case, make issues a warning, but in cases where the modifier
# includes a '=', the modifier would be interpreted as a SysV-style
# substitution like '.c=.o', and make would not issue a warning, leading to
# silent unexpected behavior.
#
# As of 2021-11-20, the actual behavior is unexpected.  Fixing it is not
# trivial.  When the assignment to 'indirect' takes place, the expressions
# from the nested expression could be preserved, like this:
#
#	Start with:
#
#		indirect:=	${INDIRECT:tl}
#
#	Since INDIRECT is defined, expand it, remembering that the modifier
#	':tl' must still be applied to the final result.
#
#		indirect:=	${LATER:S,value,replaced,} \
#				OK \
#				${LATER:value=sysv}
#
#	The variable 'LATER' is not defined.  An idea may be to append the
#	remaining modifier ':tl' to each expression that is starting with an
#	undefined variable, resulting in:
#
#		indirect:=	${LATER:S,value,replaced,:tl} \
#				OK \
#				${LATER:value=sysv:tl}
#
#	This would work for the first expression.  The second expression ends
#	with the SysV modifier ':from=to', and when this modifier is parsed,
#	it consumes all characters until the end of the expression, which in
#	this case would replace the suffix 'value' with the literal 'sysv:tl',
#	ignoring that the ':tl' was intended to be an additional modifier.
#
# Due to all of this, this surprising behavior is not easy to fix.
#
.undef LATER
.undef later
INDIRECT:=	${LATER:S,value,replaced,} OK ${LATER:value=sysv}
indirect:=	${INDIRECT:tl}
# expect+1: while evaluating variable "indirect": while evaluating variable "later": Unknown modifier "s,value,replaced,"
.if ${indirect} != " ok "
.  error
.else
# expect+1: warning: XXX Neither branch should be taken.
.  warning	XXX Neither branch should be taken.
.endif
LATER=	uppercase-value
later=	lowercase-value
# expect+1: while evaluating variable "indirect": while evaluating variable "later": Unknown modifier "s,value,replaced,"
.if ${indirect} != "uppercase-replaced ok uppercase-sysv"
# expect+1: warning: XXX Neither branch should be taken.
.  warning	XXX Neither branch should be taken.
.else
.  error
.endif


all:
	@:;