summaryrefslogtreecommitdiff
path: root/usr.bin/make/unit-tests/directive-for.mk
blob: 22b9f78e5fe1c1527e9d401df1cba7dd9def6dcc (plain)
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
293
294
295
296
297
298
299
300
301
302
303
304
305
# $NetBSD: directive-for.mk,v 1.22 2023/06/01 20:56:35 rillig Exp $
#
# Tests for the .for directive.
#
# TODO: Describe naming conventions for the loop variables.
#	.for f in values
#	.for file in values
#	.for _FILE_ in values
#	.for .FILE. in values
#	.for _f_ in values
#
# 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}
.endfor
.if ${NUMBERS} != "1 2 3"
.  error
.endif


# The .for loop also works for multiple iteration variables.
# This is something that the modifier :@ cannot do.
.for name value in VARNAME value NAME2 value2
${name}=	${value}
.endfor
.if ${VARNAME} != "value" || ${NAME2} != "value2"
.  error
.endif


# The .for loop splits the items at whitespace, taking quotes into account,
# 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
.endfor
.if ${WORDS:[#]} != 6
.  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, before interpreting the body of the .for loop, the body is
# generated by replacing each expression ${var} with ${:U1}, ${:U2} and so
# on.
#
# 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.  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
.endfor
.if ${var} != "value before"
.  warning After the .for loop, var must still have its original value.
.endif
.if ${var2} != "value before"
.  warning After the .for loop, var2 must still have its original value.
.endif

# Everything from the paragraph above also applies if the loop body is
# 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}
.endfor
.if ${var} != "value before"
.  warning After the .for loop, var must still have its original value.
.endif
.if ${var2} != "value before"
.  warning After the .for loop, var2 must still have its original value.
.endif

# 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 below expanded to:
#	EXPANSION+= value
# Since that date, the .for loop below expands to:
#	EXPANSION${:U+}= value
#
EXPANSION=		before
EXPANSION+ =		before
.for plus in +
EXPANSION${plus}=	value
.endfor
.if ${EXPANSION} != "before"
.  error This must be a make from before 2009.
.endif
.if ${EXPANSION+} != "value"
.  error This must be a make from before 2009.
.endif

# When the outer .for loop is expanded, it sees the expression ${i} and
# 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
# value literally in the body.  This could lead to situations where the
# characters from the variable value were interpreted as markup rather than
# plain text.
#
# Until 2012-06-03, the .for loop had split the words at whitespace, without
# taking quotes into account.  This made it possible to have variable values
# 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.
.for path in a:\ a:\file.txt d:\\ d:\\file.txt
.  info ${path}
.endfor
# expect-2: a:\ a:\file.txt
# expect-3: d:\\
# expect-4: d:\\file.txt


# 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 ( [ { ) ] } (()) [[]] {{}} )( ][ }{
.  info $v ${v} $(v)
.endfor
# expect-02: ( ( (
# expect-03: [ [ [
# expect-04: { { {
# expect-05: ) ) )
# expect-06: ] ] ]
# expect-07: } } }
# expect-08: (()) (()) (())
# expect-09: [[]] [[]] [[]]
# expect-10: {{}} {{}} {{}}
# expect-11: )( )( )(
# expect-12: ][ ][ ][
# expect-13: }{ }{ }{

# 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}>
.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 2023-05-09, the loop is expanded as
# usual.
# expect+1: Unknown modifier "Z"
.for var in word1 ${:Uword2:Z} word3
.  info XXX: Not reached ${var}
.endfor
# expect-2: XXX: Not reached word1
# expect-3: XXX: Not reached word3


# 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
.  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.
.for var in
.  error
.endfor

# If the iteration values become empty after expanding the expressions, the
# body of the loop is not evaluated.  It is not a parse error.
.for var in ${:U}
.  error
.endfor


# The loop body can be empty.
.for var in 1 2 3
.endfor


# A mismatched .if inside a .for loop is detected each time when the loop body
# is processed.
.for var in value
.  if 0
.endfor				# expect+0: 1 open conditional

# If there are no iteration values, the loop body is not processed, and the
# check for mismatched conditionals is not performed.
.for var in ${:U}
.  if 0
.endfor


# When a .for without the corresponding .endfor occurs in an inactive branch
# of an .if, the .for directive is just skipped, it does not even need a
# corresponding .endfor.  In other words, the behavior of the parser depends
# on the actual values of the conditions in the .if clauses.
.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


# When a .for without the corresponding .endfor occurs in an active branch of
# an .if, the parser just counts the number of .for and .endfor directives,
# without looking at any other directives.
.if 1
.  for var in value
.    endif			# expect+0: if-less endif
.  endfor			# no 'for-less endfor'
.endif				# no 'if-less endif'


# 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
.\
   for inner in i
.\
   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
.  info newline-item=(${var})
.endfor
# expect-2: newline-item=(a)