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
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 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))
ONERULE can be arbitrarily complicated… and that’s where using
eval can get tricky.
Because the results of
eval are parsed by make it can be difficult to understand exactly what is going on. You can use the
-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.
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))
t1: p1 p2 p3 build $^ > $@
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”
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.
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 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.
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 (
$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
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))
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 :-).