209

I have the following makefile for my project, and I'd like to configure it for release and debug builds. In my code, I have lots of #ifdef DEBUG macros in place, so it's simply a matter of setting this macro and adding the -g3 -gdwarf2 flags to the compilers. How can I do this?

$(CC) = g++ -g3 -gdwarf2
$(cc) = gcc -g3 -gdwarf2

all: executable

executable: CommandParser.tab.o CommandParser.yy.o Command.o
    g++ -g -o output CommandParser.yy.o CommandParser.tab.o Command.o -lfl

CommandParser.yy.o: CommandParser.l 
    flex -o CommandParser.yy.c CommandParser.l
    gcc -g -c CommandParser.yy.c

CommandParser.tab.o: CommandParser.y
    bison -d CommandParser.y
    g++ -g -c CommandParser.tab.c

Command.o: Command.cpp
    g++ -g -c Command.cpp

clean:
    rm -f CommandParser.tab.* CommandParser.yy.* output *.o

Just to clarify, when I say release/debug builds, I want to be able to just type make and get a release build or make debug and get a debug build, without manually commenting out things in the makefile.

nbro
  • 15,395
  • 32
  • 113
  • 196
samoz
  • 56,849
  • 55
  • 141
  • 195

7 Answers7

225

You can use Target-specific Variable Values. Example:

CXXFLAGS = -g3 -gdwarf2
CCFLAGS = -g3 -gdwarf2

all: executable

debug: CXXFLAGS += -DDEBUG -g
debug: CCFLAGS += -DDEBUG -g
debug: executable

executable: CommandParser.tab.o CommandParser.yy.o Command.o
    $(CXX) -o output CommandParser.yy.o CommandParser.tab.o Command.o -lfl

CommandParser.yy.o: CommandParser.l 
    flex -o CommandParser.yy.c CommandParser.l
    $(CC) -c CommandParser.yy.c

Remember to use $(CXX) or $(CC) in all your compile commands.

Then, 'make debug' will have extra flags like -DDEBUG and -g where as 'make' will not.

On a side note, you can make your Makefile a lot more concise like other posts had suggested.

Chnossos
  • 9,971
  • 4
  • 28
  • 40
David Lin
  • 5,538
  • 4
  • 25
  • 23
  • 48
    You should never change CXX or CC within a Makefile or BadThingsMayHappen (TM), those contain the path and/or name of the executables to run. CPPFLAGS, CXXFLAGS and CFLAGS serve this purpose. –  Oct 20 '11 at 08:12
  • 16
    This advice is poor because it mixes debug and non-debug object files, so that one ends up with a corrupted build. – Maxim Egorushkin Jun 07 '17 at 15:08
  • @MaximEgorushkin how to fix that? I came across this problem recently. I have a debug executable build, that was linked with release object files. Only solution so far was to declare debug and release targest phony – MauriceRandomNumber Jul 09 '18 at 09:20
  • 5
    @MauriceRandomNumber Build debug/release into its own folders. Example: https://stackoverflow.com/a/48793058/412080 – Maxim Egorushkin Jul 09 '18 at 09:23
  • I had to change -gdwarf2 to -gdwarf-2 to get it to work with clang v12.0.0 – Simon Bosley Mar 22 '21 at 09:09
  • @MauriceRandomNumber Or build from scratch (similar to `make clean`) on changing configuration: `make --always-make`. Combined with a compiler cache the perf hit is insignificant. – Andreas Jan 02 '22 at 12:58
  • 1
    This is the WORST possible workaround and unfortunately was accepted by the OP! I see this mess showing up in student projects, and my guess is that some of them copy pasted from this "accepted" answer. PLEASE DON'T DO IT THIS WAY! See the other good solutions here. – ɹɐʎɯɐʞ Apr 08 '22 at 19:28
68

This question has appeared often when searching for a similar problem, so I feel a fully implemented solution is warranted. Especially since I (and I would assume others) have struggled piecing all the various answers together.

Below is a sample Makefile which supports multiple build types in separate directories. The example illustrated shows debug and release builds.

Supports ...

  • separate project directories for specific builds
  • easy selection of a default target build
  • silent prep target to create directories needed for building the project
  • build-specific compiler configuration flags
  • GNU Make's natural method of determining if project requires a rebuild
  • pattern rules rather than the obsolete suffix rules

#
# Compiler flags
#
CC     = gcc
CFLAGS = -Wall -Werror -Wextra

#
# Project files
#
SRCS = file1.c file2.c file3.c file4.c
OBJS = $(SRCS:.c=.o)
EXE  = exefile

#
# Debug build settings
#
DBGDIR = debug
DBGEXE = $(DBGDIR)/$(EXE)
DBGOBJS = $(addprefix $(DBGDIR)/, $(OBJS))
DBGCFLAGS = -g -O0 -DDEBUG

#
# Release build settings
#
RELDIR = release
RELEXE = $(RELDIR)/$(EXE)
RELOBJS = $(addprefix $(RELDIR)/, $(OBJS))
RELCFLAGS = -O3 -DNDEBUG

.PHONY: all clean debug prep release remake

# Default build
all: prep release

#
# Debug rules
#
debug: $(DBGEXE)

$(DBGEXE): $(DBGOBJS)
    $(CC) $(CFLAGS) $(DBGCFLAGS) -o $(DBGEXE) $^

$(DBGDIR)/%.o: %.c
    $(CC) -c $(CFLAGS) $(DBGCFLAGS) -o $@ $<

#
# Release rules
#
release: $(RELEXE)

$(RELEXE): $(RELOBJS)
    $(CC) $(CFLAGS) $(RELCFLAGS) -o $(RELEXE) $^

$(RELDIR)/%.o: %.c
    $(CC) -c $(CFLAGS) $(RELCFLAGS) -o $@ $<

#
# Other rules
#
prep:
    @mkdir -p $(DBGDIR) $(RELDIR)

remake: clean all

clean:
    rm -f $(RELEXE) $(RELOBJS) $(DBGEXE) $(DBGOBJS)
ffhaddad
  • 1,653
  • 13
  • 16
  • How do you modify this to allow building of source files in a directory other than the one in which Makefile resides? – Jefferson Hudson Sep 22 '16 at 15:06
  • @JeffersonHudson If source files are in a directory named `src`, then modify the line `SRCS = file1.c file2.c file3.c file4.c` to read `SRCS = src/file1.c src/file2.c src/file3.c src/file4.c`. – zero2cx Jan 05 '17 at 16:36
  • 5
    The thing I don't like is the duplication of all the rules and variables for debug and release. I have a similar Makefile but when extending it I need to carefully copy paste each new thing for debug and release and carefully convert it. – BeeOnRope Jul 31 '18 at 05:04
  • This should be the accepted answer. I wish I'd seen this a long time ago. – Michael Dorst Apr 23 '20 at 03:48
  • This fails to track modified headers. – Bill Morgan Dec 23 '22 at 20:03
47

If by configure release/build, you mean you only need one config per makefile, then it is simply a matter and decoupling CC and CFLAGS:

CFLAGS=-DDEBUG
#CFLAGS=-O2 -DNDEBUG
CC=g++ -g3 -gdwarf2 $(CFLAGS)

Depending on whether you can use gnu makefile, you can use conditional to make this a bit fancier, and control it from the command line:

DEBUG ?= 1
ifeq ($(DEBUG), 1)
    CFLAGS =-DDEBUG
else
    CFLAGS=-DNDEBUG
endif

.o: .c
    $(CC) -c $< -o $@ $(CFLAGS)

and then use:

make DEBUG=0
make DEBUG=1

If you need to control both configurations at the same time, I think it is better to have build directories, and one build directory / config.

CharlesB
  • 86,532
  • 28
  • 194
  • 218
David Cournapeau
  • 78,318
  • 8
  • 63
  • 70
  • 19
    I don't know if I'm doing something strange, but to get the debug if statement to work (`ifeq (DEBUG, 1)`) for me, the `DEBUG` variable needed wrapped in parentheses like so: `ifeq ($(DEBUG), 1)`. – shanet Jul 26 '12 at 06:25
26

Note that you can also make your Makefile simpler, at the same time:

DEBUG ?= 1
ifeq (DEBUG, 1)
    CFLAGS =-g3 -gdwarf2 -DDEBUG
else
    CFLAGS=-DNDEBUG
endif

CXX = g++ $(CFLAGS)
CC = gcc $(CFLAGS)

EXECUTABLE = output
OBJECTS = CommandParser.tab.o CommandParser.yy.o Command.o
LIBRARIES = -lfl

all: $(EXECUTABLE)

$(EXECUTABLE): $(OBJECTS)
    $(CXX) -o $@ $^ $(LIBRARIES)

%.yy.o: %.l 
    flex -o $*.yy.c $<
    $(CC) -c $*.yy.c

%.tab.o: %.y
    bison -d $<
    $(CXX) -c $*.tab.c

%.o: %.cpp
    $(CXX) -c $<

clean:
    rm -f $(EXECUTABLE) $(OBJECTS) *.yy.c *.tab.c

Now you don't have to repeat filenames all over the place. Any .l files will get passed through flex and gcc, any .y files will get passed through bison and g++, and any .cpp files through just g++.

Just list the .o files you expect to end up with, and Make will do the work of figuring out which rules can satisfy the needs...

for the record:

  • $@ The name of the target file (the one before the colon)

  • $< The name of the first (or only) prerequisite file (the first one after the colon)

  • $^ The names of all the prerequisite files (space separated)

  • $* The stem (the bit which matches the % wildcard in the rule definition.

Stobor
  • 44,246
  • 6
  • 66
  • 69
  • You're "for the record" section has one item defined twice with different descriptions. According to http://www.gnu.org/software/make/manual/make.html#Automatic-Variables, `$^` is for all of the prerequisite files. – Grant Peters Sep 05 '10 at 00:21
  • Thanks for that Grant - typo fixed! (I checked over the Makefile, and it appears I used it correctly there, but typoed the explanation.) – Stobor Sep 05 '10 at 23:50
  • 2
    I wish there were more of these short guides to writing a reasonably small Makefiles, including the automatic variables. – AzP Oct 04 '11 at 18:57
  • It is nice to have both a debug and release target without having to change the Makefile, and the ability to pick the default based on your own preference. –  Oct 20 '11 at 08:14
  • @Barry: That's what this Makefile does: `make DEBUG=1` (or simply `make` ) will build the debug version, `make DEBUG=0` builds the release version. I can't remember why I made the default debug, but that was a long time ago. If you want the default to be non-debug, simply change the 1 in the first line to a 0. The two `make DEBUG=?` options will be unchanged, but if DEBUG is undefined (i.e. `make` ), it gets a default value in the first line. – Stobor Oct 24 '11 at 00:12
  • 2
    This solution has the problem that the debug and release output files are mixed together in the same directory. If they are not compatible, this will blow up in weird and wonderful ways unless you are careful to do a clean every time you change between debug and not. Even if they are compatible, it won't do what you expect without a clean: if you have the project built as release, and then make DEBUG=1, it will only rebuild files whose source has changed, so you won't generally get a "debug" build that way. – BeeOnRope Jul 31 '18 at 05:07
  • Line 2 should be: `ifeq ($(DEBUG), 1)` – ɹɐʎɯɐʞ Apr 08 '22 at 19:41
4

you can have a variable

DEBUG = 0

then you can use a conditional statement

  ifeq ($(DEBUG),1)

  else

  endif
Tiberiu
  • 2,870
  • 3
  • 20
  • 17
3

Completing the answers from earlier... You need to reference the variables you define info in your commands...

DEBUG ?= 1
ifeq (DEBUG, 1)
    CFLAGS =-g3 -gdwarf2 -DDEBUG
else
    CFLAGS=-DNDEBUG
endif

CXX = g++ $(CFLAGS)
CC = gcc $(CFLAGS)

all: executable

executable: CommandParser.tab.o CommandParser.yy.o Command.o
    $(CXX) -o output CommandParser.yy.o CommandParser.tab.o Command.o -lfl

CommandParser.yy.o: CommandParser.l 
    flex -o CommandParser.yy.c CommandParser.l
    $(CC) -c CommandParser.yy.c

CommandParser.tab.o: CommandParser.y
    bison -d CommandParser.y
    $(CXX) -c CommandParser.tab.c

Command.o: Command.cpp
    $(CXX) -c Command.cpp

clean:
    rm -f CommandParser.tab.* CommandParser.yy.* output *.o
Stobor
  • 44,246
  • 6
  • 66
  • 69
  • 4
    There's a (now deleted?) Answer (which should have been a Comment on an Answer) that noted `ifeq (DEBUG, 1)` should be `ifeq ($(DEBUG), 1)`. I'm guessing it may have been referring to your Answer here. – Keith M Jul 06 '18 at 19:03
3

You could also add something simple to your Makefile such as

ifeq ($(DEBUG),1)
   OPTS = -g
endif

Then compile it for debugging

make DEBUG=1

Manolete
  • 3,431
  • 7
  • 54
  • 92