# $NetBSD: var-scope-local.mk,v 1.7 2023/04/29 10:16:24 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 variable 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: Var_SetExpand: variable name "" expands to empty string, with value "three" - ignored # expect: Var_SetExpand: variable name "" expands to empty string, with value "three" - ignored one two:=three # If the two targets to the left are generated by a variable 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: : Making ${.TARGET} with VAR="${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 # 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 VAR="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 VAR="local to var-scope-local-append.o". var-scope-local-append.o: VAR += to ${.TARGET} # To access the value of a global variable, use a variable 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 VAR="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 VAR="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 VAR="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: : var-scope-local-use.o uses .USE VAR="global" a_use: .USE VAR=use : ${.TARGET} uses .USE VAR="${VAR}" all: var-scope-local-use.o var-scope-local-use.o: a_use