Reputation: 41
Here is my main program:
#include <stdio.h>
int main(void)
{
#ifdef MONO
printf("MONO is defined\n");
#elif DEBUG
printf("DEBUG is defined\n");
#else
printf("Nothing is defined\n");
#endif
return 0;
}
and my makefile:
# Makefile Variables
CC = gcc
EXEC =
CFLAGS = -Wall -g -ansi -pedantic -Werror
OBJ = test.o
# Add MONO to the CFLAGS and recompile the program
ifdef
CFLAGS += -D MONO
FANCY : clean $(EXEC)
endif
# Add DEBUG to the CFLAGS and recompile the program
ifdef
CFLAGS += -D DEBUG
DEBUG : clean $(EXEC)
endif
$(EXEC) : $(OBJ)
$(CC) $(OBJ) -o $(EXEC)
test.o : test.c
$(CC) $(CFLAGS) test.c -c
clean:
rm -f $(EXEC) $(OBJ)
Basically, I'm trying to use DEBUG and MONO for conditional compilation. The problem is that when both are undefined in my Makefile, as you can see above, it still prints "Testing... MONO is defined". I compile by typing "make" which gives me "gcc -Wall -g -ansi -pedantic -Werror test.c -c" and run by ./test
Upvotes: 0
Views: 826
Reputation: 4261
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
Upvotes: 0
Reputation: 1
Read documentation of GNU make
. You are not using ifdef
correctly.
Read also the documentation about invoking gcc
If on Linux, consider debugging your Makefile
with remake invoked with -x
. It is very helpful.
You could also consider switching to some other build automation, like ninja.
Upvotes: 1
Reputation: 41
The problem was with my makefile. As per everyone's suggestions, I edited it so ifdef wasn't by itself in the makefile and ran it like "make MONO=1" and "make DEBUG=1". The working makefile is:
# Makefile Variables
CC = gcc
EXEC = test
CFLAGS = -Wall -g -ansi -pedantic -Werror
OBJ = test.o
# Add FANCY to the CFLAGS and recompile the program
ifdef FANCY
CFLAGS += -D FANCY
FANCY : clean $(EXEC)
endif
# Add DEBUG to the CFLAGS and recompile the program
ifdef DEBUG
CFLAGS += -D DEBUG
DEBUG : clean $(EXEC)
endif
# Add DEBUG to the CFLAGS and recompile the program
ifdef MONO
CFLAGS += -D MONO
DEBUG : clean $(EXEC)
endif
$(EXEC) : $(OBJ)
$(CC) $(OBJ) -o $(EXEC)
test.o : test.c
$(CC) $(CFLAGS) test.c -c
clean:
rm -f $(EXEC) $(OBJ)
Upvotes: 0