aboutsummaryrefslogtreecommitdiff
path: root/contrib/bmake/unit-tests/var-scope-local.mk
blob: 7a031373e7da2c55e5645ca07a74c57cd07eda7b (plain) (blame)
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
# $NetBSD: var-scope-local.mk,v 1.11 2024/03/05 23:07:58 rillig Exp $
#
# Tests for target-local variables, such as ${.TARGET} or $@.  These variables
# are relatively short-lived as they are created just before making the
# target.  In contrast, global variables are typically created when the
# makefiles are read in.
#
# The 7 built-in target-local variables are listed in the manual page.  They
# are defined just before the target is actually made.  Additional
# target-local variables can be defined in dependency lines like
# 'target: VAR=value', one at a time.

.MAIN: all

# Target-local variables in a target rule
#
# In target rules, '$*' only strips the extension off the pathname if the
# extension is listed in '.SUFFIXES'.
#
# expect: target-rule.ext: * = <target-rule.ext>
all: target-rule.ext dir/subdir/target-rule.ext
target-rule.ext dir/subdir/target-rule.ext: .PHONY
	@echo '$@: @ = <${@:Uundefined}>'
	@echo '$@: % = <${%:Uundefined}>'
	@echo '$@: ? = <${?:Uundefined}>'
	@echo '$@: < = <${<:Uundefined}>'
	@echo '$@: * = <${*:Uundefined}>'

.SUFFIXES: .ir-gen-from .ir-from .ir-to

# In target rules, '$*' strips the extension off the pathname of the target
# if the extension is listed in '.SUFFIXES'.
#
# expect: target-rule.ir-gen-from: * = <target-rule>
all: target-rule.ir-gen-from dir/subdir/target-rule-dir.ir-gen-from
target-rule.ir-gen-from dir/subdir/target-rule-dir.ir-gen-from:
	@echo '$@: @ = <${@:Uundefined}>'
	@echo '$@: % = <${%:Uundefined}>'
	@echo '$@: ? = <${?:Uundefined}>'
	@echo '$@: < = <${<:Uundefined}>'
	@echo '$@: * = <${*:Uundefined}>'

.ir-from.ir-to:
	@echo '$@: @ = <${@:Uundefined}>'
	@echo '$@: % = <${%:Uundefined}>'
	@echo '$@: ? = <${?:Uundefined}>'
	@echo '$@: < = <${<:Uundefined}>'
	@echo '$@: * = <${*:Uundefined}>'
.ir-gen-from.ir-from:
	@echo '$@: @ = <${@:Uundefined}>'
	@echo '$@: % = <${%:Uundefined}>'
	@echo '$@: ? = <${?:Uundefined}>'
	@echo '$@: < = <${<:Uundefined}>'
	@echo '$@: * = <${*:Uundefined}>'

# Target-local variables in an inference rule
all: inference-rule.ir-to dir/subdir/inference-rule.ir-to
inference-rule.ir-from: .PHONY
dir/subdir/inference-rule.ir-from: .PHONY

# Target-local variables in a chain of inference rules
all: inference-rule-chain.ir-to dir/subdir/inference-rule-chain.ir-to
inference-rule-chain.ir-gen-from: .PHONY
dir/subdir/inference-rule-chain.ir-gen-from: .PHONY

# The run-time 'check' directives from above happen after the parse-time
# 'check' directives from below.
#
# expect-reset

# Deferred evaluation during parsing
#
# The target-local variables can be used in expressions, just like other
# variables.  When these expressions are evaluated outside of a target, these
# expressions are not yet expanded, instead their text is preserved, to allow
# these expressions to expand right in time when the target-local variables
# are actually set.
#
# Conditions from .if directives are evaluated in the scope of the command
# line, which means that variables from the command line, from the global
# scope and from the environment are resolved, in this precedence order (but
# see the command line option '-e').  In that phase, expressions involving
# target-local variables need to be preserved, including the exact names of
# the variables.
#
# Each of the built-in target-local variables has two equivalent names, for
# example '@' is equivalent to '.TARGET'.  The implementation might
# canonicalize these aliases at some point, and that might be surprising.
# This aliasing happens for single-character variable names like $@ or $<
# (see VarFind, CanonicalVarname), but not for braced or parenthesized
# expressions like ${@}, ${.TARGET} ${VAR:Mpattern} (see Var_Parse,
# ParseVarname).
#
# In the following condition, make expands '$@' to the long-format alias
# '$(.TARGET)'; note that the alias is not written with braces, as would be
# common in BSD makefiles, but with parentheses.  This alternative spelling
# behaves the same though.
.if $@ != "\$\(.TARGET)"
.  error
.endif
# In the long form of writing a target-local variable, the text of the
# expression is preserved exactly as written, no matter whether it is written
# with '{' or '('.
.if ${@} != "\$\{@}"
.  error
.endif
.if $(@) != "\$\(@)"
.  error
.endif
# If the expression contains modifiers, the behavior depends on the
# actual modifiers.  The modifier ':M' keeps the expression in the state
# 'undefined'.  Since the expression is still undefined after evaluating all
# the modifiers, the value of the expression is discarded and the expression
# text is used instead.  This preserves the expressions based on target-local
# variables as long as possible.
.if ${@:M*} != "\$\{@:M*}"
.  error
.endif
# In the following examples, the expressions are based on target-local
# variables but use the modifier ':L', which turns an undefined expression
# into a defined one.  At the end of evaluating the expression, the state of
# the expression is not 'undefined' anymore.  The value of the expression
# is the name of the variable, since that's what the modifier ':L' does.
.if ${@:L} != "@"
.  error
.endif
.if ${.TARGET:L} != ".TARGET"
.  error
.endif
.if ${@F:L} != "@F"
.  error
.endif
.if ${@D:L} != "@D"
.  error
.endif


# Custom local variables
#
# Additional target-local variables may be defined in dependency lines.
.MAKEFLAGS: -dv
# In the following line, the ':=' may either be interpreted as an assignment
# operator or as the dependency operator ':', followed by an empty variable
# name and the assignment operator '='.  It is the latter since in an
# assignment, the left-hand side must be a single word or empty.
#
# The empty variable name is expanded twice, once for 'one' and once for
# 'two'.
# expect: one: ignoring ' = three' as the variable name '' expands to empty
# expect: two: ignoring ' = three' as the variable name '' expands to empty
one two:=three
# If the two targets to the left are generated by an expression, the
# line is parsed as a variable assignment since its left-hand side is a single
# word.
# expect: Global: one two = three
${:Uone two}:=three
.MAKEFLAGS: -d0


.SUFFIXES: .c .o

# One of the dynamic target-local variables is '.TARGET'.  Since this is not
# a suffix transformation rule, the variable '.IMPSRC' is not defined.
# expect: : Making var-scope-local.c out of nothing.
var-scope-local.c:
	: Making ${.TARGET} ${.IMPSRC:Dfrom ${.IMPSRC}:Uout of nothing}.

# This is a suffix transformation rule, so both '.TARGET' and '.IMPSRC' are
# defined.
# expect: : Making var-scope-local.o from var-scope-local.c.
# expect: : Making basename "var-scope-local.o" in "." from "var-scope-local.c" in ".".
.c.o:
	: Making ${.TARGET} from ${.IMPSRC}.

	# The local variables @F, @D, <F, <D are legacy forms.
	# See the manual page for details.
	: Making basename "${@F}" in "${@D}" from "${<F}" in "${<D}".

# expect: : all overwritten
all: var-scope-local.o
	# The ::= modifier overwrites the .TARGET variable in the node
	# 'all', not in the global scope.  This can be seen with the -dv
	# option, looking for "all: @ = overwritten".
	: ${.TARGET} ${.TARGET::=overwritten}${.TARGET}


# Begin tests for custom target-local variables, for all 5 variable assignment
# operators.
all: var-scope-local-assign.o
all: var-scope-local-append.o
all: var-scope-local-append-global.o
all: var-scope-local-default.o
all: var-scope-local-subst.o
all: var-scope-local-shell.o

var-scope-local-assign.o \
var-scope-local-append.o \
var-scope-local-append-global.o \
var-scope-local-default.o \
var-scope-local-subst.o \
var-scope-local-shell.o:
	@echo "Making ${.TARGET} with make '"${VAR:Q}"' and env '$$VAR'."

# Target-local variables are enabled by default.  Force them to be enabled
# just in case a test above has disabled them.
.MAKE.TARGET_LOCAL_VARIABLES= yes

VAR=	global
.export VAR

# If the sources of a dependency line look like a variable assignment, make
# treats them as such.  There is only a single variable assignment per
# dependency line, which makes whitespace around the assignment operator
# irrelevant.
#
# expect-reset
# expect: Making var-scope-local-assign.o with make 'local' and env 'local'.
var-scope-local-assign.o: VAR= local

# Assignments using '+=' do *not* look up the global value, instead they only
# look up the variable in the target's own scope.
var-scope-local-append.o: VAR+= local
# Once a variable is defined in the target-local scope, appending using '+='
# behaves as expected.  Note that the expression '${.TARGET}' is not resolved
# when parsing the dependency line, its evaluation is deferred until the
# target is actually made.
# expect: Making var-scope-local-append.o with make 'local to var-scope-local-append.o' and env 'local to var-scope-local-append.o'.
var-scope-local-append.o: VAR += to ${.TARGET}
# To access the value of a global variable, use an expression.  This
# expression is expanded before parsing the whole dependency line.  Since the
# expansion happens to the right of the dependency operator ':', the expanded
# text does not influence parsing of the dependency line.  Since the expansion
# happens to the right of the assignment operator '=', the expanded text does
# not influence the parsing of the variable assignment.  The effective
# variable assignment, after expanding the whole line first, is thus
# 'VAR= global+local'.
# expect: Making var-scope-local-append-global.o with make 'global+local' and env 'global+local'.
var-scope-local-append-global.o: VAR= ${VAR}+local

var-scope-local-default.o: VAR ?= first
var-scope-local-default.o: VAR ?= second
# XXX: '?=' does look at the global variable.  That's a long-standing
# inconsistency between the assignment operators '+=' and '?='.  See
# Var_AppendExpand and VarAssign_Eval.
# expect: Making var-scope-local-default.o with make 'global' and env 'global'.

# Using the variable assignment operator ':=' provides another way of
# accessing a global variable and extending it with local modifications.  The
# '$' has to be written as '$$' though to survive the expansion of the
# dependency line as a whole.  After that, the parser sees the variable
# assignment as 'VAR := ${VAR}+local' and searches for the variable 'VAR' in
# the usual scopes, picking up the variable from the global scope.
# expect: Making var-scope-local-subst.o with make 'global+local' and env 'global+local'.
var-scope-local-subst.o: VAR := $${VAR}+local

# The variable assignment operator '!=' assigns the output of the shell
# command, as everywhere else.  The shell command is run when the dependency
# line is parsed.
var-scope-local-shell.o: VAR != echo output


# While VAR=use will be set for a .USE node, it will never be seen since only
# the ultimate target's context is searched; the variable assignments from the
# .USE target are not copied to the ultimate target's.
# expect: Making .USE var-scope-local-use.o with make 'global' and env 'global'.
a_use: .USE VAR=use
	@echo "Making .USE ${.TARGET} with make '"${VAR:Q}"' and env '$$VAR'."

all: var-scope-local-use.o
var-scope-local-use.o: a_use