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
|
# $NetBSD: varmod-assign.mk,v 1.15 2022/02/09 21:09:24 rillig Exp $
#
# Tests for the obscure ::= variable modifiers, which perform variable
# assignments during evaluation, just like the = operator in C.
all: mod-assign-empty
all: mod-assign-parse
all: mod-assign-shell-error
# The modifier '::?=' applies the assignment operator '?=' 3 times. The
# operator '?=' only has an effect for the first time, therefore the variable
# FIRST ends up with the value 1.
.if "${1 2 3:L:@i@${FIRST::?=$i}@} first=${FIRST}" != " first=1"
. error
.endif
# The modifier '::=' applies the assignment operator '=' 3 times. The
# operator '=' overwrites the previous value, therefore the variable LAST ends
# up with the value 3.
.if "${1 2 3:L:@i@${LAST::=$i}@} last=${LAST}" != " last=3"
. error
.endif
# The modifier '::+=' applies the assignment operator '+=' 3 times. The
# operator '+=' appends 3 times to the variable, therefore the variable
# APPENDED ends up with the value "1 2 3".
.if "${1 2 3:L:@i@${APPENDED::+=$i}@} appended=${APPENDED}" != " appended=1 2 3"
. error
.endif
# The modifier '::!=' applies the assignment operator '!=' 3 times. Just as
# with the modifier '::=', the last value is stored in the RAN variable.
.if "${1 2 3:L:@i@${RAN::!=${i:%=echo '<%>';}}@} ran=${RAN}" != " ran=<3>"
. error
.endif
# The assignments were performed as part of .if conditions and thus happened
# in the command line scope.
.if "${FIRST}, ${LAST}, ${APPENDED}, ${RAN}" != "1, 3, 1 2 3, <3>"
. error
.endif
# Tests for nested assignments, which are hard to read and therefore seldom
# used in practice.
# The condition "1" is true, therefore THEN1 gets assigned a value,
# and the inner IT1 as well. Nothing surprising here.
.if "${1:?${THEN1::=then1${IT1::=t1}}:${ELSE1::=else1${IE1::=e1}}} ${THEN1}${ELSE1}${IT1}${IE1}" != " then1t1"
. error
.endif
# The condition "0" is false, therefore ELSE2 gets assigned a value,
# and the inner IE2 as well. Nothing surprising here as well.
.if "${0:?${THEN2::=then2${IT2::=t2}}:${ELSE2::=else2${IE2::=e2}}} ${THEN2}${ELSE2}${IT2}${IE2}" != " else2e2"
. error
.endif
# The same effects happen when the variables are defined elsewhere.
SINK3:= ${1:?${THEN3::=then3${IT3::=t3}}:${ELSE3::=else3${IE3::=e3}}} ${THEN3}${ELSE3}${IT3}${IE3}
SINK4:= ${0:?${THEN4::=then4${IT4::=t4}}:${ELSE4::=else4${IE4::=e4}}} ${THEN4}${ELSE4}${IT4}${IE4}
.if ${SINK3} != " then3t3"
. error
.endif
.if ${SINK4} != " else4e4"
. error
.endif
mod-assign-empty:
# Assigning to the empty variable would obviously not work since that
# variable is write-protected. Therefore it is rejected early with a
# "Bad modifier" message.
@echo $@: ${::=value}
# In this variant, it is not as obvious that the name of the
# expression is empty. Assigning to it is rejected as well, with the
# same "Bad modifier" message.
@echo $@: ${:Uvalue::=overwritten}
# The :L modifier sets the value of the expression to its variable
# name. The name of the expression is "VAR", therefore assigning to
# that variable works.
@echo $@: ${VAR:L::=overwritten} VAR=${VAR}
mod-assign-parse:
# The modifier for assignment operators starts with a ':'.
# An 'x' after that is an invalid modifier.
# expect: make: Unknown modifier ":x"
@echo ${ASSIGN::x}
# When parsing an assignment operator fails because the operator is
# incomplete, make falls back to the SysV modifier.
@echo ${SYSV::=sysv\:x}${SYSV::x=:y}
@echo ${ASSIGN::=value # missing closing brace
mod-assign-shell-error:
# If the command succeeds, the variable is assigned.
@${SH_OK::!= echo word; true } echo ok=${SH_OK}
# If the command fails, the variable keeps its previous value.
@${SH_ERR::=previous}
@${SH_ERR::!= echo word; false } echo err=${SH_ERR}
# XXX: The ::= modifier expands its right-hand side exactly once.
# This differs subtly from normal assignments such as '+=' or '=', which copy
# their right-hand side literally.
APPEND.prev= previous
APPEND.var= ${APPEND.prev}
APPEND.indirect= indirect $${:Unot expanded}
APPEND.dollar= $${APPEND.indirect}
.if ${APPEND.var::+=${APPEND.dollar}} != ""
. error
.endif
.if ${APPEND.var} != "previous indirect \${:Unot expanded}"
. error
.endif
# The assignment modifier can be used in a variable expression that is
# enclosed in parentheses. In such a case, parsing stops at the first ')',
# not at the first '}'.
VAR= previous
_:= $(VAR::=current})
.if ${VAR} != "current}"
. error
.endif
# Before var.c 1.888 from 2021-03-15, an expression using the modifier '::='
# expanded its variable name once too often during evaluation. This was only
# relevant for variable names containing a '$' sign in their actual name, not
# the usual VAR.${param}.
.MAKEFLAGS: -dv
param= twice
VARNAME= VAR.$${param} # Indirect variable name because of the '$',
# to avoid difficult escaping rules.
${VARNAME}= initial-value # Sets 'VAR.${param}' to 'expanded'.
.if defined(VAR.twice) # At this point, the '$$' is not expanded.
. error
.endif
.if ${${VARNAME}::=assigned-value} # Here the variable name gets expanded once
. error # too often.
.endif
.if defined(VAR.twice)
. error The variable name in the '::=' modifier is expanded once too often.
.endif
.if ${${VARNAME}} != "assigned-value"
. error
.endif
.MAKEFLAGS: -d0
|