127 lines
5.1 KiB
Makefile
127 lines
5.1 KiB
Makefile
# $NetBSD: directive-for-empty.mk,v 1.4 2024/05/31 07:13:12 rillig Exp $
|
|
#
|
|
# Tests for .for loops containing conditions of the form 'empty(var:...)'.
|
|
#
|
|
# When a .for loop is expanded, expressions in the body of the loop
|
|
# are replaced with expressions containing the variable values. This
|
|
# replacement is a bit naive but covers most of the practical cases. The one
|
|
# popular exception is the condition 'empty(var:Modifiers)', which does not
|
|
# look like an expression and is thus not replaced.
|
|
#
|
|
# See also:
|
|
# https://gnats.netbsd.org/43821
|
|
|
|
|
|
# In the body of the .for loop, the expression '${i:M*2*}' is replaced with
|
|
# '${:U11:M*2*}', '${:U12:M*2*}', '${:U13:M*2*}', one after another. This
|
|
# replacement creates the impression that .for variables were real variables,
|
|
# when in fact they aren't.
|
|
.for i in 11 12 13
|
|
. if ${i:M*2*}
|
|
# expect+1: 2
|
|
.info 2
|
|
. endif
|
|
.endfor
|
|
|
|
|
|
# In conditions, the function call to 'empty' does not look like an
|
|
# expression, therefore it is not replaced. Since there is no global variable
|
|
# named 'i', this condition makes for a leaky abstraction. If the .for
|
|
# variables were real variables, calling 'empty' would work on them as well.
|
|
.for i in 11 12 13
|
|
# Asking for an empty iteration variable does not make sense as the .for loop
|
|
# splits the iteration items into words, and such a word cannot be empty.
|
|
. if !empty(i)
|
|
. error # not reached, due to the leaky abstraction
|
|
. endif
|
|
# The typical way of mistakenly using 'empty' with variables from .for loops
|
|
# is pattern matching using the modifiers ':M' or ':N'.
|
|
. if !empty(i:M*2*)
|
|
. error
|
|
. endif
|
|
# Instead of the 'empty' function, the variables from .for loops can be
|
|
# queried using conditions of the form '${var:...} != ""'.
|
|
. if $i == "12" && ${i:M*2*} != "12"
|
|
. error
|
|
. endif
|
|
.endfor
|
|
|
|
|
|
# The idea of replacing every occurrences of 'empty(i' in the body of a .for
|
|
# loop would be naive and require many special cases, as there are many cases
|
|
# that need to be considered when deciding whether the token 'empty' is a
|
|
# function call or not, as demonstrated by the following examples. For
|
|
# expressions like '${i:Modifiers}', this is simpler as a single
|
|
# dollar almost always starts an expression. For counterexamples and
|
|
# edge cases, see directive-for-escape.mk. Adding another such tricky detail
|
|
# is out of the question.
|
|
.MAKEFLAGS: -df
|
|
.for i in value
|
|
# The identifier 'empty' can only be used in conditions such as .if, .ifdef or
|
|
# .elif. In other lines the string 'empty(' must be preserved.
|
|
CPPFLAGS+= -Dmessage="empty(i)"
|
|
# There may be whitespace between 'empty' and '('.
|
|
.if ! empty (i)
|
|
. error
|
|
.endif
|
|
# Even in conditions, the string 'empty(' is not always a function call, it
|
|
# can occur in a string literal as well.
|
|
.if "empty\(i)" != "empty(i)"
|
|
. error
|
|
.endif
|
|
# In comments like 'empty(i)', the text must be preserved as well.
|
|
#
|
|
# Conditions, including function calls to 'empty', can not only occur in
|
|
# condition directives, they can also occur in the modifier ':?', see
|
|
# varmod-ifelse.mk.
|
|
CPPFLAGS+= -Dmacro="${empty(i):?empty:not-empty}"
|
|
.endfor
|
|
.MAKEFLAGS: -d0
|
|
|
|
|
|
# An idea to work around the above problems is to collect the variables from
|
|
# the .for loops in a separate scope. To match the current behavior, there
|
|
# has to be one scope per included file. There may be .for loops using the
|
|
# same variable name in files that include each other:
|
|
#
|
|
# outer.mk: .for i in outer
|
|
# . info $i # outer
|
|
# . include "inner.mk"
|
|
# inner.mk: . info $i # (undefined)
|
|
# . for i in inner
|
|
# . info $i # inner
|
|
# . endfor
|
|
# . info $i # (undefined)
|
|
# outer.mk: . info $i # outer
|
|
# .endfor
|
|
#
|
|
# This might be regarded another leaky abstraction, but it is in fact useful
|
|
# that variables from .for loops can only affect expressions in the current
|
|
# file. If variables from .for loops were implemented as global variables,
|
|
# they might interact between files.
|
|
#
|
|
# To emulate this exact behavior for the function 'empty', each file in the
|
|
# stack of included files needs its own scope that is independent from the
|
|
# other files.
|
|
#
|
|
# Another tricky detail are nested .for loops in a single file that use the
|
|
# same variable name. These are generally avoided by developers, as they
|
|
# would be difficult to understand for humans as well. Technically, they are
|
|
# possible though. Assuming there are two nested .for loops, both using the
|
|
# variable 'i'. When the inner .for loop ends, the inner 'i' needs to be
|
|
# removed from the scope, which would need to make the outer 'i' visible
|
|
# again. This would suggest to use one variable scope per .for loop.
|
|
#
|
|
# Using a separate scope has the benefit that Var_Parse already allows for
|
|
# a custom scope to be passed as parameter. This would have another side
|
|
# effect though. There are several modifiers that actually modify variables,
|
|
# and these modifications happen in the scope that is passed to Var_Parse.
|
|
# This would mean that the combination of a .for variable and the modifiers
|
|
# '::=', '::+=', '::?=', '::!=' and ':_' would lead to different behavior than
|
|
# before.
|
|
|
|
# TODO: Add code that demonstrates the current interaction between variables
|
|
# from .for loops and the modifiers mentioned above.
|
|
|
|
all:
|