xref: /freebsd/contrib/bmake/unit-tests/var-scope-local.mk (revision 7fdf597e96a02165cfe22ff357b857d5fa15ed8a)
1# $NetBSD: var-scope-local.mk,v 1.11 2024/03/05 23:07:58 rillig Exp $
2#
3# Tests for target-local variables, such as ${.TARGET} or $@.  These variables
4# are relatively short-lived as they are created just before making the
5# target.  In contrast, global variables are typically created when the
6# makefiles are read in.
7#
8# The 7 built-in target-local variables are listed in the manual page.  They
9# are defined just before the target is actually made.  Additional
10# target-local variables can be defined in dependency lines like
11# 'target: VAR=value', one at a time.
12
13.MAIN: all
14
15# Target-local variables in a target rule
16#
17# In target rules, '$*' only strips the extension off the pathname if the
18# extension is listed in '.SUFFIXES'.
19#
20# expect: target-rule.ext: * = <target-rule.ext>
21all: target-rule.ext dir/subdir/target-rule.ext
22target-rule.ext dir/subdir/target-rule.ext: .PHONY
23	@echo '$@: @ = <${@:Uundefined}>'
24	@echo '$@: % = <${%:Uundefined}>'
25	@echo '$@: ? = <${?:Uundefined}>'
26	@echo '$@: < = <${<:Uundefined}>'
27	@echo '$@: * = <${*:Uundefined}>'
28
29.SUFFIXES: .ir-gen-from .ir-from .ir-to
30
31# In target rules, '$*' strips the extension off the pathname of the target
32# if the extension is listed in '.SUFFIXES'.
33#
34# expect: target-rule.ir-gen-from: * = <target-rule>
35all: target-rule.ir-gen-from dir/subdir/target-rule-dir.ir-gen-from
36target-rule.ir-gen-from dir/subdir/target-rule-dir.ir-gen-from:
37	@echo '$@: @ = <${@:Uundefined}>'
38	@echo '$@: % = <${%:Uundefined}>'
39	@echo '$@: ? = <${?:Uundefined}>'
40	@echo '$@: < = <${<:Uundefined}>'
41	@echo '$@: * = <${*:Uundefined}>'
42
43.ir-from.ir-to:
44	@echo '$@: @ = <${@:Uundefined}>'
45	@echo '$@: % = <${%:Uundefined}>'
46	@echo '$@: ? = <${?:Uundefined}>'
47	@echo '$@: < = <${<:Uundefined}>'
48	@echo '$@: * = <${*:Uundefined}>'
49.ir-gen-from.ir-from:
50	@echo '$@: @ = <${@:Uundefined}>'
51	@echo '$@: % = <${%:Uundefined}>'
52	@echo '$@: ? = <${?:Uundefined}>'
53	@echo '$@: < = <${<:Uundefined}>'
54	@echo '$@: * = <${*:Uundefined}>'
55
56# Target-local variables in an inference rule
57all: inference-rule.ir-to dir/subdir/inference-rule.ir-to
58inference-rule.ir-from: .PHONY
59dir/subdir/inference-rule.ir-from: .PHONY
60
61# Target-local variables in a chain of inference rules
62all: inference-rule-chain.ir-to dir/subdir/inference-rule-chain.ir-to
63inference-rule-chain.ir-gen-from: .PHONY
64dir/subdir/inference-rule-chain.ir-gen-from: .PHONY
65
66# The run-time 'check' directives from above happen after the parse-time
67# 'check' directives from below.
68#
69# expect-reset
70
71# Deferred evaluation during parsing
72#
73# The target-local variables can be used in expressions, just like other
74# variables.  When these expressions are evaluated outside of a target, these
75# expressions are not yet expanded, instead their text is preserved, to allow
76# these expressions to expand right in time when the target-local variables
77# are actually set.
78#
79# Conditions from .if directives are evaluated in the scope of the command
80# line, which means that variables from the command line, from the global
81# scope and from the environment are resolved, in this precedence order (but
82# see the command line option '-e').  In that phase, expressions involving
83# target-local variables need to be preserved, including the exact names of
84# the variables.
85#
86# Each of the built-in target-local variables has two equivalent names, for
87# example '@' is equivalent to '.TARGET'.  The implementation might
88# canonicalize these aliases at some point, and that might be surprising.
89# This aliasing happens for single-character variable names like $@ or $<
90# (see VarFind, CanonicalVarname), but not for braced or parenthesized
91# expressions like ${@}, ${.TARGET} ${VAR:Mpattern} (see Var_Parse,
92# ParseVarname).
93#
94# In the following condition, make expands '$@' to the long-format alias
95# '$(.TARGET)'; note that the alias is not written with braces, as would be
96# common in BSD makefiles, but with parentheses.  This alternative spelling
97# behaves the same though.
98.if $@ != "\$\(.TARGET)"
99.  error
100.endif
101# In the long form of writing a target-local variable, the text of the
102# expression is preserved exactly as written, no matter whether it is written
103# with '{' or '('.
104.if ${@} != "\$\{@}"
105.  error
106.endif
107.if $(@) != "\$\(@)"
108.  error
109.endif
110# If the expression contains modifiers, the behavior depends on the
111# actual modifiers.  The modifier ':M' keeps the expression in the state
112# 'undefined'.  Since the expression is still undefined after evaluating all
113# the modifiers, the value of the expression is discarded and the expression
114# text is used instead.  This preserves the expressions based on target-local
115# variables as long as possible.
116.if ${@:M*} != "\$\{@:M*}"
117.  error
118.endif
119# In the following examples, the expressions are based on target-local
120# variables but use the modifier ':L', which turns an undefined expression
121# into a defined one.  At the end of evaluating the expression, the state of
122# the expression is not 'undefined' anymore.  The value of the expression
123# is the name of the variable, since that's what the modifier ':L' does.
124.if ${@:L} != "@"
125.  error
126.endif
127.if ${.TARGET:L} != ".TARGET"
128.  error
129.endif
130.if ${@F:L} != "@F"
131.  error
132.endif
133.if ${@D:L} != "@D"
134.  error
135.endif
136
137
138# Custom local variables
139#
140# Additional target-local variables may be defined in dependency lines.
141.MAKEFLAGS: -dv
142# In the following line, the ':=' may either be interpreted as an assignment
143# operator or as the dependency operator ':', followed by an empty variable
144# name and the assignment operator '='.  It is the latter since in an
145# assignment, the left-hand side must be a single word or empty.
146#
147# The empty variable name is expanded twice, once for 'one' and once for
148# 'two'.
149# expect: one: ignoring ' = three' as the variable name '' expands to empty
150# expect: two: ignoring ' = three' as the variable name '' expands to empty
151one two:=three
152# If the two targets to the left are generated by an expression, the
153# line is parsed as a variable assignment since its left-hand side is a single
154# word.
155# expect: Global: one two = three
156${:Uone two}:=three
157.MAKEFLAGS: -d0
158
159
160.SUFFIXES: .c .o
161
162# One of the dynamic target-local variables is '.TARGET'.  Since this is not
163# a suffix transformation rule, the variable '.IMPSRC' is not defined.
164# expect: : Making var-scope-local.c out of nothing.
165var-scope-local.c:
166	: Making ${.TARGET} ${.IMPSRC:Dfrom ${.IMPSRC}:Uout of nothing}.
167
168# This is a suffix transformation rule, so both '.TARGET' and '.IMPSRC' are
169# defined.
170# expect: : Making var-scope-local.o from var-scope-local.c.
171# expect: : Making basename "var-scope-local.o" in "." from "var-scope-local.c" in ".".
172.c.o:
173	: Making ${.TARGET} from ${.IMPSRC}.
174
175	# The local variables @F, @D, <F, <D are legacy forms.
176	# See the manual page for details.
177	: Making basename "${@F}" in "${@D}" from "${<F}" in "${<D}".
178
179# expect: : all overwritten
180all: var-scope-local.o
181	# The ::= modifier overwrites the .TARGET variable in the node
182	# 'all', not in the global scope.  This can be seen with the -dv
183	# option, looking for "all: @ = overwritten".
184	: ${.TARGET} ${.TARGET::=overwritten}${.TARGET}
185
186
187# Begin tests for custom target-local variables, for all 5 variable assignment
188# operators.
189all: var-scope-local-assign.o
190all: var-scope-local-append.o
191all: var-scope-local-append-global.o
192all: var-scope-local-default.o
193all: var-scope-local-subst.o
194all: var-scope-local-shell.o
195
196var-scope-local-assign.o \
197var-scope-local-append.o \
198var-scope-local-append-global.o \
199var-scope-local-default.o \
200var-scope-local-subst.o \
201var-scope-local-shell.o:
202	@echo "Making ${.TARGET} with make '"${VAR:Q}"' and env '$$VAR'."
203
204# Target-local variables are enabled by default.  Force them to be enabled
205# just in case a test above has disabled them.
206.MAKE.TARGET_LOCAL_VARIABLES= yes
207
208VAR=	global
209.export VAR
210
211# If the sources of a dependency line look like a variable assignment, make
212# treats them as such.  There is only a single variable assignment per
213# dependency line, which makes whitespace around the assignment operator
214# irrelevant.
215#
216# expect-reset
217# expect: Making var-scope-local-assign.o with make 'local' and env 'local'.
218var-scope-local-assign.o: VAR= local
219
220# Assignments using '+=' do *not* look up the global value, instead they only
221# look up the variable in the target's own scope.
222var-scope-local-append.o: VAR+= local
223# Once a variable is defined in the target-local scope, appending using '+='
224# behaves as expected.  Note that the expression '${.TARGET}' is not resolved
225# when parsing the dependency line, its evaluation is deferred until the
226# target is actually made.
227# 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'.
228var-scope-local-append.o: VAR += to ${.TARGET}
229# To access the value of a global variable, use an expression.  This
230# expression is expanded before parsing the whole dependency line.  Since the
231# expansion happens to the right of the dependency operator ':', the expanded
232# text does not influence parsing of the dependency line.  Since the expansion
233# happens to the right of the assignment operator '=', the expanded text does
234# not influence the parsing of the variable assignment.  The effective
235# variable assignment, after expanding the whole line first, is thus
236# 'VAR= global+local'.
237# expect: Making var-scope-local-append-global.o with make 'global+local' and env 'global+local'.
238var-scope-local-append-global.o: VAR= ${VAR}+local
239
240var-scope-local-default.o: VAR ?= first
241var-scope-local-default.o: VAR ?= second
242# XXX: '?=' does look at the global variable.  That's a long-standing
243# inconsistency between the assignment operators '+=' and '?='.  See
244# Var_AppendExpand and VarAssign_Eval.
245# expect: Making var-scope-local-default.o with make 'global' and env 'global'.
246
247# Using the variable assignment operator ':=' provides another way of
248# accessing a global variable and extending it with local modifications.  The
249# '$' has to be written as '$$' though to survive the expansion of the
250# dependency line as a whole.  After that, the parser sees the variable
251# assignment as 'VAR := ${VAR}+local' and searches for the variable 'VAR' in
252# the usual scopes, picking up the variable from the global scope.
253# expect: Making var-scope-local-subst.o with make 'global+local' and env 'global+local'.
254var-scope-local-subst.o: VAR := $${VAR}+local
255
256# The variable assignment operator '!=' assigns the output of the shell
257# command, as everywhere else.  The shell command is run when the dependency
258# line is parsed.
259var-scope-local-shell.o: VAR != echo output
260
261
262# While VAR=use will be set for a .USE node, it will never be seen since only
263# the ultimate target's context is searched; the variable assignments from the
264# .USE target are not copied to the ultimate target's.
265# expect: Making .USE var-scope-local-use.o with make 'global' and env 'global'.
266a_use: .USE VAR=use
267	@echo "Making .USE ${.TARGET} with make '"${VAR:Q}"' and env '$$VAR'."
268
269all: var-scope-local-use.o
270var-scope-local-use.o: a_use
271