Here is how to solve the problem without MAKECMDGOALS
introspection. Tha problem is basically that the rules you specify in Makefile
constitute a static graph. The target-specific assignments are used during the execution of rule bodies, but not during their compilation.
The solution to this is to grab control over rule compilation: use GNU Make's macro-like constructs to generate the rules. Then we have full control: we can stick variable material into the target, prerequisite or recipe.
Here is my version of your Makefile
all:
@echo specify configuration 'debug' or 'release'
OBJS := foo.o bar.o
# BUILDDIR is a macro
# $(call BUILDDIR,WORD) -> .build/WORD
BUILDDIR = .build/$(1)
# target is a macro
# $(call TARGET,WORD) -> ./build/WORD/foo.o ./build/WORD/bar.o
TARGET = $(addprefix $(call BUILDDIR,$(1))/,$(OBJS))
# BUILDRULE is a macro: it builds a release or debug rule
# or whatever word we pass as argument $(1)
define BUILDRULE
$(call BUILDDIR,$(1))/%.o: %.c
@echo [$(call BUILDDIR,$(1))/$$*.o] should be [$$@]
@mkdir -p $$(dir $$@)
$$(CC) -c -DMODE=$(1) $$< -o $$@
endef
debug: $(call TARGET,debug)
release: $(call TARGET,release)
# generate two build rules from macro
$(eval $(call BUILDRULE,debug))
$(eval $(call BUILDRULE,release))
clean:
rm -rf .build
Now, notice the advantage: I can build both debug
and release
targets in one go, because I have instantiated both rules from the template!
$ make clean ; make debug release
rm -rf .build
[.build/debug/foo.o] should be [.build/debug/foo.o]
cc -c -DMODE=debug foo.c -o .build/debug/foo.o
[.build/debug/bar.o] should be [.build/debug/bar.o]
cc -c -DMODE=debug bar.c -o .build/debug/bar.o
[.build/release/foo.o] should be [.build/release/foo.o]
cc -c -DMODE=release foo.c -o .build/release/foo.o
[.build/release/bar.o] should be [.build/release/bar.o]
cc -c -DMODE=release bar.c -o .build/release/bar.o
Furthermore, I have taken liberty to add the macro argument into the cc
command line also, so that the modules receive a MODE
macro which tells them how they are being compiled.
We can use variable indirection to set up different CFLAGS
or whatever. Watch what happens if we patch the above like this:
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,9 @@
OBJS := foo.o bar.o
+CFLAGS_debug = -O0 -g
+CFLAGS_release = -O2
+
# BUILDDIR is a macro
# $(call BUILDDIR,WORD) -> .build/WORD
BUILDDIR = .build/$(1)
@@ -17,7 +20,7 @@ define BUILDRULE
$(call BUILDDIR,$(1))/%.o: %.c
@echo [$(call BUILDDIR,$(1))/$$*.o] should be [$$@]
@mkdir -p $$(dir $$@)
- $$(CC) -c -DMODE=$(1) $$< -o $$@
+ $$(CC) -c $$(CFLAGS_$(1)) -DMODE=$(1) $$< -o $$@
endef
debug: $(call TARGET,debug)
Run:
$ make clean ; make debug release
rm -rf .build
[.build/debug/foo.o] should be [.build/debug/foo.o]
cc -c -O0 -g -DMODE=debug foo.c -o .build/debug/foo.o
[.build/debug/bar.o] should be [.build/debug/bar.o]
cc -c -O0 -g -DMODE=debug bar.c -o .build/debug/bar.o
[.build/release/foo.o] should be [.build/release/foo.o]
cc -c -O2 -DMODE=release foo.c -o .build/release/foo.o
[.build/release/bar.o] should be [.build/release/bar.o]
cc -c -O2 -DMODE=release bar.c -o .build/release/bar.o
Finally, we can combine that with MAKECMDGOALS
. We can inspect MAKECMDGOALS
and filter out the build modes which are not specified there. If make release
is called, we don't need the debug
rules to be expanded. Patch:
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,11 @@
OBJS := foo.o bar.o
+# List of build types, but only those mentioned on command line
+BUILD_TYPES := $(filter $(MAKECMDGOALS),debug release)
+
+$(warning "generating rules for BUILD_TYPES := $(BUILD_TYPES)")
+
CFLAGS_debug = -O0 -g
CFLAGS_release = -O2
@@ -17,18 +22,15 @@ TARGET = $(addprefix $(call BUILDDIR,$(1))/,$(OBJS))
# BUILDRULE is a macro: it builds a release or debug rule
# or whatever word we pass as argument $(1)
define BUILDRULE
+$(1): $(call TARGET,$(1))
$(call BUILDDIR,$(1))/%.o: %.c
@echo [$(call BUILDDIR,$(1))/$$*.o] should be [$$@]
@mkdir -p $$(dir $$@)
$$(CC) -c $$(CFLAGS_$(1)) -DMODE=$(1) $$< -o $$@
endef
-debug: $(call TARGET,debug)
-release: $(call TARGET,release)
-
-# generate two build rules from macro
-$(eval $(call BUILDRULE,debug))
-$(eval $(call BUILDRULE,release))
+$(foreach type,$(BUILD_TYPES),\
+ $(eval $(call BUILDRULE,$(type))))
clean:
rm -rf .build
Note that I simplified things by rolling the debug:
and release:
targets into the BUILDRULE
macro.
$ make clean ; make release
Makefile:9: "generating rules for BUILD_TYPES := "
rm -rf .build
Makefile:9: "generating rules for BUILD_TYPES := release"
[.build/release/foo.o] should be [.build/release/foo.o]
cc -c -O2 -DMODE=release foo.c -o .build/release/foo.o
[.build/release/bar.o] should be [.build/release/bar.o]
cc -c -O2 -DMODE=release bar.c -o .build/release/bar.o
$ make clean ; make release debug
Makefile:9: "generating rules for BUILD_TYPES := "
rm -rf .build
Makefile:9: "generating rules for BUILD_TYPES := debug release"
[.build/release/foo.o] should be [.build/release/foo.o]
cc -c -O2 -DMODE=release foo.c -o .build/release/foo.o
[.build/release/bar.o] should be [.build/release/bar.o]
cc -c -O2 -DMODE=release bar.c -o .build/release/bar.o
[.build/debug/foo.o] should be [.build/debug/foo.o]
cc -c -O0 -g -DMODE=debug foo.c -o .build/debug/foo.o
[.build/debug/bar.o] should be [.build/debug/bar.o]
cc -c -O0 -g -DMODE=debug bar.c -o .build/debug/bar.o