Metaprogramming is the ability of a program generate other programs, or even modify itself while running. This is the sixth in a series of posts exploring how to use metaprogramming in GNU make makefiles, discussing the eval
function.
The eval
Function
In previous posts we’ve discussed the concepts of evaluation and expansion, and ways to build lists of prerequisites from constructed variable names. Last time we discussed a way to create entirely new makefiles, programmatically, using constructed include files. That’s a very powerful capability but it has drawbacks. In this post we’ll discuss the most powerful, but also trickiest to get right, metaprogramming technique yet: GNU make’s eval
function.
The eval
function allows you to ask make to parse some text that you provide (as the argument to the function) as if it were a makefile. You can think of this as similar to the include
directive except instead of including a separate makefile, we “include” the provided text. The eval
function is evaluated using standard procedures, and the result of the expansion is always the empty string. This means that the eval function can appear essentially anywhere inside a makefile.
As a very simple example, these two lines have identical behavior:
VAR := one
$(eval VAR := one)
Of course there’s no point in doing that. You may recall, however that creating multi-line included makefiles was problematic because a multi-line macro could not be used within a recipe. Multi-line macros can be given to eval
, however, and they work just as you’d expect:
define ONERULE $(TARGET): $(PREREQUISITES) build $(PREREQUISITES) > $(TARGET) endef TARGET = t1 PREREQUISITES = p1 p2 p3 $(eval $(ONERULE)) TARGET = t2 PREREQUISITES = r1 r2 r3 $(eval $(ONERULE))
Of course ONERULE
can be arbitrarily complicated… and that’s where using eval
can get tricky.
Debugging the eval
Function
Because the results of eval
are parsed by make it can be difficult to understand exactly what is going on. You can use the --print-database
(-p
) option to show the results of parsing the entire makefile, but that both gives too much information and at the same time not enough detail.
A better solution is to use GNU make’s info
function, which will print its argument to the terminal. The info
function uses identical expansion and evaluation methods as the eval
function, even expanding to the empty string just as eval
does. As a result replacing the eval
function with an info
function, leaving everything else the same, will print out the text that will be evaluated. In the example above we could write:
define ONERULE $(TARGET): $(PREREQUISITES) build $(PREREQUISITES) > $(TARGET) endef TARGET = t1 PREREQUISITES = p1 p2 p3 $(info $(ONERULE)) $(eval $(ONERULE)) TARGET = t2 PREREQUISITES = r1 r2 r3 $(info $(ONERULE)) $(eval $(ONERULE))
which would result in output like this:
t1: p1 p2 p3 build p1 p2 p3 > t1 t2: r1 r2 r3 build r1 r2 r3 > t2
You can do even more; for example using the warning
function instead of info
will print the makefile name and line number as well, although the formatting will be a little wonky. You could use the value
function to show the “before” text as well; for example $(info '$(value ONERULE)' expands to '$(ONERULE)')
. However, for most debugging sessions just adding an invocation of info
will be sufficient.
How eval
Evaluates
You will notice in our example above we don’t use automatic variables in the recipe of ONERULE
. Now that we have a useful debugging method, we can examine how using automatic variables might change things:
define ONERULE $(TARGET): $(PREREQUISITES) build $^ > $@ endef TARGET = t1 PREREQUISITES = p1 p2 p3 $(info $(ONERULE))
The result of the info function will be:
t1: p1 p2 p3 build >
Whoa, where did our arguments go? The problem is that the value passed to eval
is expanded before it is evaluated; that means that all variables are expanded before the text is parsed as a makefile, when our normal immediate/deferred expansion rules come into effect. This gives a lot of flexibility, but at a cost: variables you want to be expanded before evaluation should be written as normal variables. Variables you want to be expanded during or after evaluation (as part of makefile parsing or while running recipes) must be escaped:
define ONERULE $(TARGET): $(PREREQUISITES) build $$^ > $$@ endef TARGET = t1 PREREQUISITES = p1 p2 p3 $(info $(ONERULE))
Yields:
t1: p1 p2 p3 build $^ > $@
N-ary Evaluation
You can think of any macro in GNU make as a nullary function: a function that takes no arguments. The expanded value of the macro can be thought of as the returned value from the function. Although the macro does not take arguments in the traditional sense, it can certainly contain references to other macros; in essence, global variables.
In our examples above the ONERULE
“function” contained references to “global variables” TARGET
and PREREQUISITES
. Thus, when we wanted to evaluate ONERULE
with different target/prerequisite pairs, we had to reset the global variables before each invocation of eval
. This is clearly less than ideal.
The foreach
Function
There is a way in GNU make to create a macro which acts as a unary function (a function that takes a single argument): the foreach
function. foreach
has a single localized loop variable: thus any macro expanded in the context of a foreach
function which uses the loop variable can be considered to take a single argument. We can even utilize constructed macro names to parameterize multiple variables using the single loop variable, like so:
define ONERULE $T: $($T_PREREQUISITES) build $$^ > $$@ endef TARGETS = t1 t2 t1_PREREQUISITES = p1 p2 p3 t2_PREREQUISITES = r1 r2 r3 $(foreach T,$(TARGETS),$(eval $(ONERULE)))
This is very useful, and sufficient for many uses of eval
. However, more complex uses may require multiple arguments and using constructed variable names is not always convenient.
The call
Function
There is another feature in GNU make that comes to our rescue here and is commonly used in conjunction with the eval
function: the call
function. The call
function allows you to create your own n-ary user-defined function by defining a normal macro that uses special variables ($1
, $2
, etc.) to represent the arguments. To invoke the user-defined function you pass the name of the macro to the call
function along with the values for the arguments.
A very simple example of a function that reverses its arguments might be:
REVERSE = $2 $1
When you use the call
function to invoke REVERSE
the first argument will be used in place of $1
and the second argument will be used in place of $2
, and the resulting expansion will be to reverse them:
$ cat Makefile
REVERSE = $2 $1
all: ; @echo '$(call REVERSE,first,second)'
$ make
second first
The call
function is immensely powerful in its own right and probably deserves a separate discussion; it can invoke built-in functions, other user-defined macros, and can even be recursive. However, for now we’re mainly interested in call
because it can be combined with eval
to produce complete metaprogramming environments.
An alternate way to write our ONERULE
macro with call
might be:
define ONERULE $1: $2 build $$^ > $$@ endef $(eval $(call ONERULE,t1,p1 p2 p3)) $(eval $(call ONERULE,t2,r1 r2 r3))
Note how call
takes the name ONERULE
as the first argument, not the expansion of the variable. Further note how the arguments to call
are delimited by commas, not whitespace, allowing arguments to contain whitespace. As before we need to escape the automatic variables so they won’t be expanded too early.
Many people prefer to use call
even for unary functions, rather than foreach
, as they prefer to use the more generic $1
rather than require a specific loop variable. Of course you could always write your foreach loop using 1
as the loop variable, as well :-).