Metaprogramming is the ability of a program generate other programs, or even modify itself while running. This is the third in a series of posts exploring how to use metaprogramming in GNU make makefiles, discussing constructed macro names.
Constructed Macro Names
This facility is the simplest to understand, and one of the most useful. It’s also mostly portable across a wide array of
make implementations, which is nice (although always keep in mind Paul’s First Rule 🙂 ). However, it still conforms to the rules of expansion and evaluation which means that it cannot fundamentally alter the behavior of make; in that sense it’s arguably not really “metaprogramming”, but we’ll start with it anyway.
A simple macro assignment and reference, as we know, looks like this:
MY_VAR = my value all: ; @echo '$(MY_VAR)'
The important idea for this post is that the text constituting the name of the macro itself is expanded, before the macro name is looked up!
Consider this makefile:
foo_TEXT = foo bar_TEXT = bar all: foo bar foo bar: ; @echo "$@ text is $($@_TEXT)"
$($@_TEXT) is a constructed macro name. Make notices the first dollar sign/open parenthesis and realizes that it introduces a macro reference. It then collects all the text up to the matching close parenthesis:
$@_TEXT. Instead of immediately attempting to find a macro with that name, make will first expand that text. In this case, as we know
$@ is an automatic variable which make will set to the name of the target (
$@_TEXT will expand to
foo_TEXT when building the target
bar_TEXT when building the target
foo text is foo bar text is bar
This can be an extremely powerful capability, allowing recipes to be customized on a per-target basis without having to write separate rules. If you wanted to allow extra compilation flags to be added to the recipe only for certain object files, you could write a generic pattern rule then define explicit rules for each target which needed a different set of flags. Obviously this is annoying from many perspectives. Using constructed macro names instead, we can use a single pattern rule for all objects; for example:
SRCS = foo.c bar.c biz.c baz.c %.o : %.c $(CC) $(CPPFLAGS) $(CFLAGS) $($*_FLAGS) -c -o $@ $< all: $(SRCS:%.c=%.o) bar_FLAGS = -Wno-error baz_FLAGS = -I/opt/baz/include
When make invokes the recipe to create the target
bar.o it will add
-Wno-error to the compile line, and when it invokes the recipe to create
baz.o it will add
-I/opt/baz/include to the compile line. Constructs like this allow for a more "data-driven" makefile implementation, where the build is controlled more by variable settings and less by explicit rules.
Constructed macro names can also be useful in the context of looping constructs such as the
foreach function; given the above makefile we could imagine adding a debugging statement:
$(foreach S,$(SRCS),$(info extra flags for $S: $($(basename $S)_FLAGS)))
which would give the output:
extra flags for foo.c: extra flags for bar.c: -Wno-error extra flags for biz.c: extra flags for baz.c: -I/opt/baz/include
A popular alternative for some uses of constructed variable names are target-specific variables. These can be used to set macro values which are in effect only in the context of a specific target. The above example could be equivalently written, with target-specific variables, as:
SRCS = foo.c bar.c biz.c baz.c %.o : %.c $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< all: $(SRCS:%.c=%.o) bar.o : CFLAGS += -Wno-error baz.o : CPPFLAGS += -I/opt/baz/include
Target-specific variables have an additional feature which can be quite powerful: they are inherited by all prerequisites of that target. So for example you can create a top-level target
debug that adds debugging flags to all compilations:
all: prog prog: prog.o foo.o bar.o biz.o baz.o debug: all debug: CFLAGS += -g debug: CPPFLAGS += -DDEBUG
make debug will cause the
all target and its prerequisites to be built (since
debug depends on
all), and all these targets inherit the settings for
On the other hand, target-specific variable values are available only within the recipe of their target. This means they're not available for external debugging statements such as the
foreach loop example above. It also means they're not useful for more advanced metaprogramming techniques.
The main limitation on constructed macro names is that they still must obey the normal rules for immediate and deferred expansion. For example, users often try to do something like this:
foo_OBJECTS = foo.o baz.o biz.o bar_OBJECTS = bar.o baz.o boz.o foo bar: $($@_OBJECTS)
However, this cannot work because the prerequisite list is expanded in an immediate context, and the automatic variables like
$@ are not set until much later, when make wants to invoke the recipe. So
$@ (most likely) expands to the empty string, and
$($@_OBJECTS) expands to the value of the macro
_OBJECTS, which is likely not set either.
Target-specific variables have this same limitation, since as noted above their values are available only within the context of a recipe, not when expanding and evaluating prerequisites in an immediate context.
In the next post we'll examine a feature used to overcome the limitation on using automatic variables in prerequisite lists: secondary expansion.