Bug in GNU make: target-specific variables are not expanded in implicit rules

gnugnu-makemakefile

I have been working on designing a multiple configuration Makefile (one that supports separate 'debug' and 'release' targets), and have come across a strange problem which appears to be a bug in GNU make.

It seems that GNU make is not expanding target-specific variables properly when those variables are referenced in an implicit rule. Here is a simplified Makefile which shows this issue:

all:
    @echo specify configuration 'debug' or 'release'

OBJS := foo.o bar.o

BUILDDIR = .build/$(CONFIG)

TARGET = $(addprefix $(BUILDDIR)/,$(OBJS))

debug: CONFIG := debug
release: CONFIG := release

#CONFIG := debug

debug: $(TARGET)
release: $(TARGET)

clean:
    rm -rf .build

$(BUILDDIR)/%.o: %.c
    @echo [$(BUILDDIR)/$*.o] should be [$@]
    @mkdir -p $(dir $@)
    $(CC) -c $< -o $@

When specifying the goal 'debug' to make, CONFIG is set to 'debug', and BUILDDIR and TARGET are likewise expanded properly. However, in the implicit rule to build the source file from the object, $@ is expanded as if CONFIG does not exist.

Here is the output from using this Makefile:

$ make debug
[.build/debug/foo.o] should be [.build//foo.o]
cc -c foo.c -o .build//foo.o
[.build/debug/bar.o] should be [.build//bar.o]
cc -c bar.c -o .build//bar.o

This shows that BUILDDIR is being expanded fine, but the resulting $@ is not. If I then comment out the target variable specification and manually set CONFIG := debug (the commented line above), I get what I would expect:

$ make debug
[.build/debug/foo.o] should be [.build/debug/foo.o]
cc -c foo.c -o .build/debug/foo.o
[.build/debug/bar.o] should be [.build/debug/bar.o]
cc -c bar.c -o .build/debug/bar.o

I've tested this with both make-3.81 on Gentoo and MinGW, and make-3.82 on Gentoo. All exhibit the same behavior.

I find it difficult to believe that I would be the first to come across this problem, so I'm guessing I'm probably just doing something wrong — but I'll be honest: I don't see how I could be. 🙂

Are there any make gurus out there that might be able to shed some light on this issue? Thanks!

Best Answer

Basically, Make works out the DAG of dependencies and creates a list of rules that must be run before running any rule. Assigning a target-specific value is something that Make does when running a rule, which comes later. This is a serious limitation (which I and others have complained about before), but I wouldn't call it a bug since it is described in the documentation. According to the GNUMake manual:

6.11 Target-specific Variable Values: "As with automatic variables, these values are only available within the context of a target's recipe (and in other target-specific assignments)."

And "the context of a target's recipe" means the commands, not the prereqs:

10.5.3 Automatic variables: "[Automatic variables] cannot be accessed directly within the prerequisite list of a rule."

There are a couple of ways around this. You can use Secondary Expansion, if your version of Make GNUMake has it (3.81 doesn't, I don't know about 3.82). Or you can do without target-specific variables:

DEBUG_OBJS = $(addprefix $(BUILDDIR)/debug/,$(OBJS))
RELEASE_OBJS = $(addprefix $(BUILDDIR)/release/,$(OBJS))

debug: % : $(DEBUG_OBJS)
release: $(RELEASE_OBJS)

$(DEBUG_OBJS): $(BUILDDIR)/debug/%.o : %.cc
$(RELEASE_OBJS): $(BUILDDIR)/release/%.o : %.cc

$(DEBUG_OBJS) $(RELEASE_OBJS):
    @echo making $@ from $^
    @mkdir -p $(dir $@)                                                        
    $(CC) -c $< -o $@ 
Related Topic