Home | History | Annotate | Line # | Download | only in unit-tests
directive-for.mk revision 1.31
      1 # $NetBSD: directive-for.mk,v 1.31 2025/06/28 22:39:28 rillig Exp $
      2 #
      3 # Tests for the .for directive.
      4 #
      5 # TODO: Describe naming conventions for the loop variables.
      6 #	.for f in values
      7 #	.for file in values
      8 #	.for _FILE_ in values
      9 #	.for .FILE. in values
     10 #	.for _f_ in values
     11 #
     12 # See also:
     13 #	varmod-loop.mk		The ':@var (a] ...@' modifier
     14 
     15 # A typical use case for a .for loop is to populate a variable with a list of
     16 # values depending on other variables.  In simple cases, the same effect can
     17 # be achieved using the ':@var@${var}@' modifier.
     18 .undef NUMBERS
     19 .for num in 1 2 3
     20 NUMBERS+=	${num}
     21 .endfor
     22 .if ${NUMBERS} != "1 2 3"
     23 .  error
     24 .endif
     25 
     26 
     27 # The .for loop also works for multiple iteration variables.
     28 # This is something that the modifier :@ cannot do as easily.
     29 .for name value in VARNAME value NAME2 value2
     30 ${name}=	${value}
     31 .endfor
     32 .if ${VARNAME} != "value" || ${NAME2} != "value2"
     33 .  error
     34 .endif
     35 
     36 
     37 # The .for loop splits the items at whitespace, taking quotes into account,
     38 # just like the :M or :S modifiers.
     39 #
     40 # Until 2012-06-03, the .for loop had split the items exactly at whitespace,
     41 # without taking the quotes into account.  This had resulted in 10 words.
     42 .undef WORDS
     43 .for var in one t\ w\ o "three three" 'four four' `five six`
     44 WORDS+=	counted
     45 .endfor
     46 .if ${WORDS:[#]} != 6
     47 .  error
     48 .endif
     49 
     50 
     51 # In the body of the .for loop, the iteration variables can be accessed
     52 # like normal variables, even though they are not really variables.
     53 #
     54 # Instead, before interpreting the body of the .for loop, the body is
     55 # generated by replacing each expression ${var} with ${:U1}, ${:U2} and so
     56 # on.
     57 #
     58 # A noticeable effect of this implementation technique is that the .for
     59 # iteration variables and the normal global variables live in separate
     60 # namespaces and do not influence each other.  The "scope" of the .for loop
     61 # variables is restricted to the current makefile, it does not reach over to
     62 # any included makefiles.
     63 var=	value before
     64 var2=	value before
     65 .for var var2 in 1 2 3 4
     66 .endfor
     67 .if ${var} != "value before"
     68 .  warning After the .for loop, var must still have its original value.
     69 .endif
     70 .if ${var2} != "value before"
     71 .  warning After the .for loop, var2 must still have its original value.
     72 .endif
     73 
     74 # Everything from the paragraph above also applies if the loop body is
     75 # empty.  In this particular example, the items to be iterated are empty as
     76 # well.
     77 var=	value before
     78 var2=	value before
     79 .for var var2 in ${:U}
     80 .endfor
     81 .if ${var} != "value before"
     82 .  warning After the .for loop, var must still have its original value.
     83 .endif
     84 .if ${var2} != "value before"
     85 .  warning After the .for loop, var2 must still have its original value.
     86 .endif
     87 
     88 # Before for.c 1.39 from 2008-12-21, the values of the iteration variables
     89 # were simply inserted as plain text and then parsed as usual, which made it
     90 # possible to achieve all kinds of strange effects, such as generating '.if'
     91 # directives or inserting '$' characters in random places, thereby changing
     92 # how following '$' are interpreted.
     93 #
     94 # Before that date, the .for loop below expanded to:
     95 #	EXPANSION+= value
     96 # Since that date, the .for loop below expands to:
     97 #	EXPANSION${:U+}= value
     98 #
     99 EXPANSION=		before
    100 EXPANSION+ =		before
    101 .for plus in +
    102 EXPANSION${plus}=	value
    103 .endfor
    104 .if ${EXPANSION} != "before"
    105 .  error This must be a make from before 2009.
    106 .endif
    107 .if ${EXPANSION+} != "value"
    108 .  error This must be a make from before 2009.
    109 .endif
    110 
    111 # When the outer .for loop is expanded, it sees the expression ${i} and
    112 # expands it.  The inner loop then only sees the expression ${:Uouter} and
    113 # has nothing more to expand.
    114 .for i in outer
    115 .  for i in inner
    116 # expect+1: outer
    117 .    info ${i}
    118 .  endfor
    119 .endfor
    120 
    121 
    122 # From https://gnats.netbsd.org/29985.
    123 #
    124 # Until 2008-12-21, the .for loop was expanded by replacing the variable
    125 # value literally in the body.  This could lead to situations where the
    126 # characters from the variable value were interpreted as markup rather than
    127 # plain text.
    128 #
    129 # Until 2012-06-03, the .for loop had split the words at whitespace, without
    130 # taking quotes into account.  This made it possible to have variable values
    131 # like "a:\ a:\file.txt" that ended in a single backslash.  Since then, the
    132 # variable values have been replaced with expressions of the form ${:U...},
    133 # which are not interpreted as code anymore.
    134 .for path in a:\ a:\file.txt d:\\ d:\\file.txt
    135 # expect+3: a:\ a:\file.txt
    136 # expect+2: d:\\
    137 # expect+1: d:\\file.txt
    138 .  info ${path}
    139 .endfor
    140 
    141 
    142 # Ensure that braces and parentheses are properly escaped by the .for loop.
    143 # Each line must print the same word 3 times.
    144 # See ForLoop_SubstBody.
    145 .for v in ( [ { ) ] } (()) [[]] {{}} )( ][ }{
    146 # expect+12: ( ( (
    147 # expect+11: [ [ [
    148 # expect+10: { { {
    149 # expect+9: ) ) )
    150 # expect+8: ] ] ]
    151 # expect+7: } } }
    152 # expect+6: (()) (()) (())
    153 # expect+5: [[]] [[]] [[]]
    154 # expect+4: {{}} {{}} {{}}
    155 # expect+3: )( )( )(
    156 # expect+2: ][ ][ ][
    157 # expect+1: }{ }{ }{
    158 .  info $v ${v} $(v)
    159 .endfor
    160 
    161 # Before 2023-05-09, the variable names could contain arbitrary characters,
    162 # except for whitespace, allowing for creative side effects, as usual for
    163 # arbitrary code injection.
    164 var=	outer
    165 # expect+1: Invalid character ":" in .for loop variable name
    166 .for var:Q in value "quoted"
    167 .  info <${var}> <${var:Q}> <${var:Q:Q}>
    168 .endfor
    169 
    170 # Before 2023-05-09, when variable names could contain '$', the short
    171 # expression '$$' was preserved, the long expressions were substituted.
    172 # expect+1: Invalid character "$" in .for loop variable name
    173 .for $ in value
    174 .  info <$$> <${$}> <$($)>
    175 .endfor
    176 
    177 
    178 # https://gnats.netbsd.org/53146 mentions the idea of using a dynamic
    179 # variable name in .for loops, based on some other variable.  The .for loops
    180 # are already tricky enough to understand in detail, even without this
    181 # possibility, therefore the variable names are restricted to using harmless
    182 # characters only.
    183 INDIRECT=	direct
    184 # expect+1: Invalid character "$" in .for loop variable name
    185 .for $(INDIRECT) in value
    186 # If the variable name could be chosen dynamically, the iteration variable
    187 # might have been 'direct', thereby expanding the expression '${direct}'.
    188 .  info <$(INDIRECT)> <$(direct)> <$($(INDIRECT))>
    189 .endfor
    190 
    191 
    192 # Regular global variables and the "variables" from the .for loop don't
    193 # interfere with each other.  In the following snippet, the variable 'DIRECT'
    194 # is used both as a global variable, as well as an iteration variable in the
    195 # .for loop.  The expression '${INDIRECT}' refers to the global variable, not
    196 # to the one from the .for loop.
    197 DIRECT=		global
    198 INDIRECT=	${DIRECT}
    199 .for DIRECT in iteration
    200 .  if "${DIRECT} ${INDIRECT}" != "iteration global"
    201 .    error
    202 .  endif
    203 .endfor
    204 
    205 
    206 # An empty list of variables to the left of the 'in' is a parse error.
    207 # expect+1: Missing iteration variables in .for loop
    208 .for in value
    209 .  error
    210 .endfor
    211 
    212 # An empty list of iteration values to the right of the 'in' is accepted.
    213 # Unlike in the shell, it is not a parse error.
    214 .for var in
    215 .  error
    216 .endfor
    217 
    218 # If the iteration values become empty after expanding the expressions, the
    219 # body of the loop is not evaluated.  It is not a parse error.
    220 .for var in ${:U}
    221 .  error
    222 .endfor
    223 
    224 
    225 # The loop body can be empty.
    226 .for var in 1 2 3
    227 .endfor
    228 
    229 
    230 # A mismatched .if inside a .for loop is detected each time when the loop body
    231 # is processed.
    232 .for var in value
    233 .  if 0
    234 .endfor				# expect+0: 1 open conditional
    235 
    236 # If there are no iteration values, the loop body is not processed, and the
    237 # check for mismatched conditionals is not performed.
    238 .for var in ${:U}
    239 .  if 0
    240 .endfor
    241 
    242 
    243 # When a .for without the corresponding .endfor occurs in an inactive branch
    244 # of an .if, the .for directive is just skipped, it does not even need a
    245 # corresponding .endfor.  In other words, the behavior of the parser depends
    246 # on the actual values of the conditions in the .if clauses.
    247 .if 0
    248 .  for var in value		# does not need a corresponding .endfor
    249 .endif
    250 .endfor				# expect+0: for-less endfor
    251 .endif				# expect+0: if-less endif
    252 
    253 
    254 # When a .for without the corresponding .endfor occurs in an active branch of
    255 # an .if, the parser just counts the number of .for and .endfor directives,
    256 # without looking at any other directives.
    257 .if 1
    258 .  for var in value
    259 .    endif			# expect+0: if-less endif
    260 .  endfor			# no 'for-less endfor'
    261 .endif				# no 'if-less endif'
    262 
    263 
    264 # Before for.c 1.172 from 2023-05-08, when make parsed a .for loop, it
    265 # assumed that there was no line continuation between the '.' and the 'for'
    266 # or 'endfor', as there is no practical reason to break the line at this
    267 # point.
    268 #
    269 # When make scanned the outer .for loop, it did not recognize the inner .for
    270 # loop as such and instead treated it as an unknown directive.  The body of
    271 # the outer .for loop thus ended above the '.endfor'.
    272 #
    273 # When make scanned the inner .for loop, it did not recognize the inner
    274 # .endfor as such, which led to a parse error 'Unexpected end of file in .for
    275 # loop' from the '.endfor' line, followed by a second parse error 'for-less
    276 # .endfor' from the '.\\n endfor' line.
    277 .MAKEFLAGS: -df
    278 .for outer in o
    279 .\
    280    for inner in i
    281 .\
    282    endfor
    283 .endfor
    284 .MAKEFLAGS: -d0
    285 
    286 
    287 # When there is a variable definition 'scope=cmdline' from the command line
    288 # (which has higher precedence than global variables) and a .for loop iterates
    289 # over a variable of the same name, the expression '${scope}' expands to the
    290 # value from the .for loop.  This is because when the body of the .for loop is
    291 # expanded, the expression '${scope}' is textually replaced with ${:Uloop}',
    292 # without resolving any other variable names (ForLoop_SubstBody).  Later, when
    293 # the body of the .for loop is actually interpreted, the body text doesn't
    294 # contain the word 'scope' anymore.
    295 .MAKEFLAGS: scope=cmdline
    296 .for scope in loop
    297 .  if ${scope} != "loop"
    298 .    error
    299 .  endif
    300 .endfor
    301 
    302 
    303 # Since at least 1993, iteration stops at the first newline.
    304 # Back then, the .newline variable didn't exist, therefore it was unlikely
    305 # that a newline ever occurred.
    306 .for var in a${.newline}b${.newline}c
    307 # expect+1: newline-item=(a)
    308 .  info newline-item=(${var})
    309 .endfor
    310