Alex Rivera | Logout

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

Asked 2010-11-01T13:57:37.867
15

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

Edit
Report

1 Answer

9

As Beta has pointed out, this indeed isn't a bug in make since the limitation is described in the documentation (I guess I must have missed that particular part -- sorry).

In any case, I was actually able to work around this issue by doing something even simpler. Since all I need is to assign a variable based on the goal, I found that I can use the $(MAKECMDGOALS) variable in order to expand the build directory properly. Eliminating the $(CONFIG) variable and rewriting the Makefile as the following does exactly what I need:

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

OBJS := foo.o bar.o

BUILDDIR := .build/$(MAKECMDGOALS)

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

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

clean:
        rm -rf .build

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

This then gives the proper result:

$ 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
$ make release
[.build/release/foo.o] should be [.build/release/foo.o]
cc -c foo.c -o .build/release/foo.o
[.build/release/bar.o] should be [.build/release/bar.o]
cc -c bar.c -o .build/release/bar.o
$ make debug
make: Nothing to be done for `debug'.
$ make release
make: Nothing to be done for `release'.

This will of course break if there are multiple goals specified on the command line (since $(MAKECMDGOALS) contains a space-separated list), but dealing with that isn't too much of a problem.

answered 2010-11-02T06:57:25.323

Your Answer