Home | History | Annotate | Line # | Download | only in unit-tests
directive-for-empty.mk revision 1.4
      1  1.4  rillig # $NetBSD: directive-for-empty.mk,v 1.4 2024/05/31 07:13:12 rillig Exp $
      2  1.1  rillig #
      3  1.1  rillig # Tests for .for loops containing conditions of the form 'empty(var:...)'.
      4  1.1  rillig #
      5  1.3  rillig # When a .for loop is expanded, expressions in the body of the loop
      6  1.1  rillig # are replaced with expressions containing the variable values.  This
      7  1.1  rillig # replacement is a bit naive but covers most of the practical cases.  The one
      8  1.1  rillig # popular exception is the condition 'empty(var:Modifiers)', which does not
      9  1.3  rillig # look like an expression and is thus not replaced.
     10  1.1  rillig #
     11  1.1  rillig # See also:
     12  1.1  rillig #	https://gnats.netbsd.org/43821
     13  1.1  rillig 
     14  1.1  rillig 
     15  1.1  rillig # In the body of the .for loop, the expression '${i:M*2*}' is replaced with
     16  1.1  rillig # '${:U11:M*2*}', '${:U12:M*2*}', '${:U13:M*2*}', one after another.  This
     17  1.1  rillig # replacement creates the impression that .for variables were real variables,
     18  1.1  rillig # when in fact they aren't.
     19  1.1  rillig .for i in 11 12 13
     20  1.1  rillig .  if ${i:M*2*}
     21  1.2  rillig # expect+1: 2
     22  1.1  rillig .info 2
     23  1.1  rillig .  endif
     24  1.1  rillig .endfor
     25  1.1  rillig 
     26  1.1  rillig 
     27  1.3  rillig # In conditions, the function call to 'empty' does not look like an
     28  1.1  rillig # expression, therefore it is not replaced.  Since there is no global variable
     29  1.4  rillig # named 'i', this condition makes for a leaky abstraction.  If the .for
     30  1.1  rillig # variables were real variables, calling 'empty' would work on them as well.
     31  1.1  rillig .for i in 11 12 13
     32  1.1  rillig # Asking for an empty iteration variable does not make sense as the .for loop
     33  1.1  rillig # splits the iteration items into words, and such a word cannot be empty.
     34  1.4  rillig .  if !empty(i)
     35  1.4  rillig .    error			# not reached, due to the leaky abstraction
     36  1.1  rillig .  endif
     37  1.4  rillig # The typical way of mistakenly using 'empty' with variables from .for loops
     38  1.4  rillig # is pattern matching using the modifiers ':M' or ':N'.
     39  1.1  rillig .  if !empty(i:M*2*)
     40  1.4  rillig .    error
     41  1.4  rillig .  endif
     42  1.4  rillig # Instead of the 'empty' function, the variables from .for loops can be
     43  1.4  rillig # queried using conditions of the form '${var:...} != ""'.
     44  1.4  rillig .  if $i == "12" && ${i:M*2*} != "12"
     45  1.4  rillig .    error
     46  1.1  rillig .  endif
     47  1.1  rillig .endfor
     48  1.1  rillig 
     49  1.1  rillig 
     50  1.1  rillig # The idea of replacing every occurrences of 'empty(i' in the body of a .for
     51  1.1  rillig # loop would be naive and require many special cases, as there are many cases
     52  1.1  rillig # that need to be considered when deciding whether the token 'empty' is a
     53  1.1  rillig # function call or not, as demonstrated by the following examples.  For
     54  1.3  rillig # expressions like '${i:Modifiers}', this is simpler as a single
     55  1.3  rillig # dollar almost always starts an expression.  For counterexamples and
     56  1.1  rillig # edge cases, see directive-for-escape.mk.  Adding another such tricky detail
     57  1.1  rillig # is out of the question.
     58  1.1  rillig .MAKEFLAGS: -df
     59  1.1  rillig .for i in value
     60  1.1  rillig # The identifier 'empty' can only be used in conditions such as .if, .ifdef or
     61  1.1  rillig # .elif.  In other lines the string 'empty(' must be preserved.
     62  1.1  rillig CPPFLAGS+=	-Dmessage="empty(i)"
     63  1.1  rillig # There may be whitespace between 'empty' and '('.
     64  1.1  rillig .if ! empty (i)
     65  1.1  rillig .  error
     66  1.1  rillig .endif
     67  1.1  rillig # Even in conditions, the string 'empty(' is not always a function call, it
     68  1.1  rillig # can occur in a string literal as well.
     69  1.1  rillig .if "empty\(i)" != "empty(i)"
     70  1.1  rillig .  error
     71  1.1  rillig .endif
     72  1.1  rillig # In comments like 'empty(i)', the text must be preserved as well.
     73  1.1  rillig #
     74  1.1  rillig # Conditions, including function calls to 'empty', can not only occur in
     75  1.1  rillig # condition directives, they can also occur in the modifier ':?', see
     76  1.1  rillig # varmod-ifelse.mk.
     77  1.1  rillig CPPFLAGS+=	-Dmacro="${empty(i):?empty:not-empty}"
     78  1.1  rillig .endfor
     79  1.1  rillig .MAKEFLAGS: -d0
     80  1.1  rillig 
     81  1.1  rillig 
     82  1.1  rillig # An idea to work around the above problems is to collect the variables from
     83  1.1  rillig # the .for loops in a separate scope.  To match the current behavior, there
     84  1.1  rillig # has to be one scope per included file.  There may be .for loops using the
     85  1.1  rillig # same variable name in files that include each other:
     86  1.1  rillig #
     87  1.1  rillig # outer.mk:	.for i in outer
     88  1.1  rillig #		.  info $i		# outer
     89  1.1  rillig #		.  include "inner.mk"
     90  1.1  rillig # inner.mk:	.    info $i		# (undefined)
     91  1.1  rillig #		.    for i in inner
     92  1.1  rillig #		.      info $i		# inner
     93  1.1  rillig #		.    endfor
     94  1.1  rillig #		.    info $i		# (undefined)
     95  1.1  rillig # outer.mk:	.  info $i		# outer
     96  1.1  rillig #		.endfor
     97  1.1  rillig #
     98  1.1  rillig # This might be regarded another leaky abstraction, but it is in fact useful
     99  1.1  rillig # that variables from .for loops can only affect expressions in the current
    100  1.1  rillig # file.  If variables from .for loops were implemented as global variables,
    101  1.1  rillig # they might interact between files.
    102  1.1  rillig #
    103  1.1  rillig # To emulate this exact behavior for the function 'empty', each file in the
    104  1.1  rillig # stack of included files needs its own scope that is independent from the
    105  1.1  rillig # other files.
    106  1.1  rillig #
    107  1.1  rillig # Another tricky detail are nested .for loops in a single file that use the
    108  1.1  rillig # same variable name.  These are generally avoided by developers, as they
    109  1.1  rillig # would be difficult to understand for humans as well.  Technically, they are
    110  1.1  rillig # possible though.  Assuming there are two nested .for loops, both using the
    111  1.1  rillig # variable 'i'.  When the inner .for loop ends, the inner 'i' needs to be
    112  1.1  rillig # removed from the scope, which would need to make the outer 'i' visible
    113  1.1  rillig # again.  This would suggest to use one variable scope per .for loop.
    114  1.1  rillig #
    115  1.1  rillig # Using a separate scope has the benefit that Var_Parse already allows for
    116  1.1  rillig # a custom scope to be passed as parameter.  This would have another side
    117  1.1  rillig # effect though.  There are several modifiers that actually modify variables,
    118  1.1  rillig # and these modifications happen in the scope that is passed to Var_Parse.
    119  1.1  rillig # This would mean that the combination of a .for variable and the modifiers
    120  1.1  rillig # '::=', '::+=', '::?=', '::!=' and ':_' would lead to different behavior than
    121  1.1  rillig # before.
    122  1.1  rillig 
    123  1.1  rillig # TODO: Add code that demonstrates the current interaction between variables
    124  1.1  rillig #  from .for loops and the modifiers mentioned above.
    125  1.4  rillig 
    126  1.4  rillig all:
    127