Metaprogramming is the ability of a program generate other programs, or even modify itself while running. This is the second in a series of posts exploring how to use metaprogramming in GNU make makefiles, discussing recursive expansion.
In the first post in this series, we introduced the concepts of evaluation and expansion. From the standpoint of metaprogramming the most interesting thing about expansion is that it can be recursive. This means that after a macro is expanded, the result of the expansion is checked to see if it can be further expanded. Only once no more expansion is possible is the final value available for evaluation.
Take this example makefile:
FOO = foo BAR = bar $(FOO) BAZ = baz $(BAR) $(info "BAZ is '$(BAZ)'")
Because we’re using standard make assignment (
=), the right-hand-sides of the first three lines are not expanded before evaluation and the macros
BAZ are set to the unexpanded values that appear after the assignment operator.
When make reaches the fourth line it needs to be expanded before it can be evaluated. Make sees that there’s a dollar sign followed by open parenthesis so it knows that the macro reference up to the matching close parenthesis must be expanded. This macro reference turns out to be an invocation of the
info function, and the string being passed to the function must be expanded.
Within the string is
'$(BAZ)' which contains a dollar sign/open parenthesis, so that needs to be expanded as well. The expansion of
'$(BAZ)' will be
'baz $(BAR)'. The macro
BAR will be expanded to
bar $(FOO) (yielding
'baz bar $(FOO)'), and then the macro
FOO will be expanded to
foo, so the final result of expanding
'baz bar foo'.
Thus the string after expansion is “BAZ is ‘baz bar foo'”, and this (including the quotes) is what will be printed by the
It’s important to realize that when make expands a string it replaces a macro reference (introduced by a dollar sign) wherever it appears. The dollar sign doesn’t need to be the first character in a string, or even the first character in a word in the string.
Sometimes, you don’t want things to be expanded even if they contain a dollar sign and they appear in a location where make would expand them. There are a few options available to you to avoid expansion.
The first, and most common, option is to double the dollar signs: this is known as escaping the dollar sign. When make sees a string like
$$X, instead of considering this a macro reference to
X which must be expanded, make “expands” this by removing one of the dollar signs and moving on. Thus the expansion of
$$X is the literal string
$X. Similarly, the expansion of
$$(FOO) is the literal string
$(FOO), not the expansion of the macro
The second, less commonly used option is the
value function. This function takes the name of a variable, and it expands to the unexpanded value of that variable. One interesting thing about this is that the argument to the value function is expanded first, and the result of that is treated as the name of the variable whose value we get.
An example may help: given this makefile (note how the double-dollar signs are used to avoid expanding values in the output statement):
f = booh FOO = $foo BAR = FOO $(info 1. FOO is '$(FOO)') $(info 2. BAR is '$(BAR)') $(info 3. $$(value FOO) is '$(value FOO)') $(info 4. $$(value BAR) is '$(value BAR)') $(info 5. $$(value $$(BAR)) is '$(value $(BAR))')
the resulting output will be:
1. FOO is 'boohoo' 2. BAR is 'FOO' 3. $(value FOO) is '$foo' 4. $(value BAR) is 'FOO' 5. $(value $(BAR)) is '$foo'
Note that in the last line,
$(BAR) is expanded to
FOO, which is then treated as the name of the variable to take the value of.
Although it seems like this would be very handy, in reality it ends up not being that useful because it’s an “all or nothing” operation. Most of the time what you really want is some parts of a value to be expanded and other parts to not be expanded. The double-dollar sign allows you to choose exactly what to expand, providing that flexibility.
Now that we’ve covered expansion in detail, it’s time to examine our first metaprogramming technique: constructed macro names.