I see a bunch of potential problems with this approach. The most important thing is that you may have part of your objects compiled with one define and others without it, which may be desirable, but only if you are fully aware of it. Consider the following:
$ cat Makefile
CC = gcc
CFLAGS = -Wall -g -ansi -pedantic -Werror
OBJ = test.o ident.o
EXEC = test
ifdef FANCY
CFLAGS += -DFANCY
FANCY : clean $(EXEC)
endif
ifdef DEBUG
CFLAGS += -DDEBUG
DEBUG : clean $(EXEC)
endif
ifdef MONO
CFLAGS += -DMONO
MONO : clean $(EXEC)
endif
$(EXEC): $(OBJ)
$(CC) $(OBJ) -o $(EXEC)
clean:
rm -f $(EXEC) $(OBJ)
$ cat test.c
#include <stdio.h>
#include "ident.h"
int main(int argc, char *argv[]) {
printf("%s\n", ident());
return 0;
}
$ cat ident.c
#include "ident.h"
const char *ident(void) {
#ifdef MONO
return "MONO is defined";
#elif DEBUG
return "DEBUG is defined";
#else
return "Nothing is defined";
#endif
}
Since there is a DEBUG target defined, I would imagine that I can call make DEBUG to compile it in debug mode, right?
$ make DEBUG
make: *** No rule to make target 'DEBUG'. Stop.
Nope. Target DEBUG does not exist until additional variable is defined:
$ make DEBUG DEBUG=1
rm -f test test.o ident.o
gcc -Wall -g -ansi -pedantic -Werror -DDEBUG -c -o test.o test.c
gcc -Wall -g -ansi -pedantic -Werror -DDEBUG -c -o ident.o ident.c
gcc test.o ident.o -o test
Another tricky thing is that defining DEBUG changes default target from $(EXEC) to DEBUG, which is counterintuitive - suddenly by defining a variable you change what make is going to build, not only how. This is supposed to be determined by targets, not variables. Note also that since DEBUG is now default, it triggers clean which may not be desirable (and in fact is not enforced when building without DEBUG). Therefore whenever I work on debug version, I need to call make every time with DEBUG=1 and it always cleans everything (might be quite annoying with hundreds of files to compile).
The greatest thing is that when you switch from debug mode back to release, clean is not enforced, leaving some objects from debug version linked with others built in release mode. See for yourself:
$ make DEBUG=1
rm -f test test.o ident.o
gcc -Wall -g -ansi -pedantic -Werror -DDEBUG -c -o test.o test.c
gcc -Wall -g -ansi -pedantic -Werror -DDEBUG -c -o ident.o ident.c
gcc test.o ident.o -o test
$ touch test.c
$ make
gcc -Wall -g -ansi -pedantic -Werror -c -o test.o test.c
gcc test.o ident.o -o test
$ ./test
DEBUG is defined
In this example ident.c was not recompiled and it was reused from debug version. Quite easy to overlook in regular development.
Personally, for such cases I would compile every supported mode in a separate directory, like this:
$ cat Makefile.new
CC = gcc
CFLAGS = -Wall -g -ansi -pedantic -Werror
OBJ = test.o ident.o
EXEC = test
MODES := RELEASE FANCY DEBUG MONO
define mode_template
.PHONY: all
all: $(1)
.PHONY: $(1)
$(1): $(1)/$(EXEC)
$(1)/$(EXEC): $(addprefix $(1)/,$(OBJ))
$$(LINK.o) $$(OUTPUT_OPTION) $$^
$(addprefix $(1)/,$(OBJ)): CFLAGS += -D$(1)
$(addprefix $(1)/,$(OBJ)): $(1)/%.o: %.c | $(1)/.
$$(COMPILE.c) $$(OUTPUT_OPTION) $$<
$(1)/.:
mkdir -p $$(@D)
.PHONY: clean
clean::
-rm -f $(1)/$(EXEC) $(addprefix $(1)/,$(OBJ))
endef
$(foreach mode,$(MODES),$(eval $(call mode_template,$(mode))))
This allows to build all supported modes at once (useful to check if they all still build):
$ make -f Makefile.new
mkdir -p RELEASE
gcc -Wall -g -ansi -pedantic -Werror -DRELEASE -c -o RELEASE/test.o test.c
gcc -Wall -g -ansi -pedantic -Werror -DRELEASE -c -o RELEASE/ident.o ident.c
gcc -o RELEASE/test RELEASE/test.o RELEASE/ident.o
mkdir -p FANCY
gcc -Wall -g -ansi -pedantic -Werror -DFANCY -c -o FANCY/test.o test.c
gcc -Wall -g -ansi -pedantic -Werror -DFANCY -c -o FANCY/ident.o ident.c
gcc -o FANCY/test FANCY/test.o FANCY/ident.o
mkdir -p DEBUG
gcc -Wall -g -ansi -pedantic -Werror -DDEBUG -c -o DEBUG/test.o test.c
gcc -Wall -g -ansi -pedantic -Werror -DDEBUG -c -o DEBUG/ident.o ident.c
gcc -o DEBUG/test DEBUG/test.o DEBUG/ident.o
mkdir -p MONO
gcc -Wall -g -ansi -pedantic -Werror -DMONO -c -o MONO/test.o test.c
gcc -Wall -g -ansi -pedantic -Werror -DMONO -c -o MONO/ident.o ident.c
gcc -o MONO/test MONO/test.o MONO/ident.o
Every version is compiled differently:
$ ./RELEASE/test
Nothing is defined
$ ./FANCY/test
Nothing is defined
$ ./DEBUG/test
DEBUG is defined
$ ./MONO/test
MONO is defined
It does not erase everything on each call:
$ make -f Makefile.new
make: Nothing to be done for 'all'.
It recompiles only changed files without mixing them up or erasing everything:
$ touch test.c
$ make -f Makefile.new
gcc -Wall -g -ansi -pedantic -Werror -DRELEASE -c -o RELEASE/test.o test.c
gcc -o RELEASE/test RELEASE/test.o RELEASE/ident.o
gcc -Wall -g -ansi -pedantic -Werror -DFANCY -c -o FANCY/test.o test.c
gcc -o FANCY/test FANCY/test.o FANCY/ident.o
gcc -Wall -g -ansi -pedantic -Werror -DDEBUG -c -o DEBUG/test.o test.c
gcc -o DEBUG/test DEBUG/test.o DEBUG/ident.o
gcc -Wall -g -ansi -pedantic -Werror -DMONO -c -o MONO/test.o test.c
gcc -o MONO/test MONO/test.o MONO/ident.o
It supports parallel compilation (which you cannot be sure when having clean and $(EXEC) on the same dependency list):
$ make -f Makefile.new -j4
mkdir -p RELEASE
mkdir -p FANCY
mkdir -p DEBUG
mkdir -p MONO
gcc -Wall -g -ansi -pedantic -Werror -DRELEASE -c -o RELEASE/test.o test.c
gcc -Wall -g -ansi -pedantic -Werror -DRELEASE -c -o RELEASE/ident.o ident.c
gcc -Wall -g -ansi -pedantic -Werror -DFANCY -c -o FANCY/test.o test.c
gcc -Wall -g -ansi -pedantic -Werror -DFANCY -c -o FANCY/ident.o ident.c
gcc -Wall -g -ansi -pedantic -Werror -DDEBUG -c -o DEBUG/test.o test.c
gcc -Wall -g -ansi -pedantic -Werror -DDEBUG -c -o DEBUG/ident.o ident.c
gcc -Wall -g -ansi -pedantic -Werror -DMONO -c -o MONO/test.o test.c
gcc -Wall -g -ansi -pedantic -Werror -DMONO -c -o MONO/ident.o ident.c
gcc -o RELEASE/test RELEASE/test.o RELEASE/ident.o
gcc -o FANCY/test FANCY/test.o FANCY/ident.o
gcc -o DEBUG/test DEBUG/test.o DEBUG/ident.o
gcc -o MONO/test MONO/test.o MONO/ident.o
And you can still build only one config at a time:
$ make -f Makefile.new DEBUG
mkdir -p DEBUG
gcc -Wall -g -ansi -pedantic -Werror -DDEBUG -c -o DEBUG/test.o test.c
gcc -Wall -g -ansi -pedantic -Werror -DDEBUG -c -o DEBUG/ident.o ident.c
gcc -o DEBUG/test DEBUG/test.o DEBUG/ident.o
#ifdef SOMETHING ... #else ... #endif- Daniel Walkerifdefs in your makefile? - Daniel Walker#elsewill only be included ifSOMETHINGis not defined. - Daniel Walker