Using GNU Make to build both debug and release targets at the same time

gnu-makemakefile

I'm working on a medium sized project which contains several libraries with interdependence's which I've recently converted over to build using a non-recursive makefile. My next goal is to enable building of both debug and release builds out of the same source tree at the same time (make debug;make release). My first step was to make debug and release targets which contained the correct build flags. I did this using target specific variables, like this:
CXXFLAGS=-Wall -Wextra -Werror -DLINUX

CXX_DEBUG_FLAGS=-g3 -DDEBUG_ALL
CXX_RELEASE_FLAGS=-O3

.PHONY: debug 
debug: CXXFLAGS+=$(CXX_DEBUG_FLAGS) 
debug: build

.PHONY: release 
release: CXXFLAGS+=$(CXX_RELEASE_FLAGS) 
release: build

This worked fine, but you could only build debug, or release, not both at the same time. And by same time, I don't mean during the same build I mean back to back in the same source tree (make debug;make release). In order to do this I need to place the object files in a debug/release specific directory so they don't overwrite each other and I need to mangle the debug target binary name with a 'D'. I though this would be easy as I could just use target specific variables again, like this:
CXXFLAGS=-Wall -Wextra -Werror -DLINUX

CXX_DEBUG_FLAGS=-g3 -DDEBUG_ALL
CXX_RELEASE_FLAGS=-O3

.PHONY: debug 
debug: CXXFLAGS+=$(CXX_DEBUG_FLAGS) 
debug: MODULE_BLD_TYPE=D
debug: OUT_DIR=debug_obj
debug: build

.PHONY: release 
release: CXXFLAGS+=$(CXX_RELEASE_FLAGS) 
release: MODULE_BLD_TYPE:=
release: OUT_DIR=release_obj
release: build

.PHONY: build
build: TARGET_NAME=HelloWorld$(MODULE_BLD_TYPE)
build: TARGET_BUILD_DIR=$(PROJECT_ROOT_DIR)/$(OUT_DIR)
build: TARGET_BUILD_OBJS=$(addprefix $(TARGET_BUILD_DIR)/,$(SOURCES:.cpp=.o))
build: $(TARGET_NAME)

You make experts reading this already know this won't work because you can't use target specific variables to create actual targets. They worked fine for my CXXFLAGS var because the variable wasn't used in a target name.

Is there a design pattern and or best practice to managing debug/release builds using non-recursive makefiles? Specificly, how do I build the object file directory path and target name (build a target based on a target)?

Best Answer

One of the most persistent problems with Make is its inability to handle more than one wildcard at a time. There is no really clean way to do what you ask (without resorting to recursion, which I don't think is really so bad). Here is a reasonable approach:

CXXFLAGS=-Wall -Wextra -Werror -DLINUX
CXX_DEBUG_FLAGS=-g3 -DDEBUG_ALL 
CXX_RELEASE_FLAGS=-O3 

.PHONY: debug  
debug: CXXFLAGS+=$(CXX_DEBUG_FLAGS)  
debug: HelloWorldD

.PHONY: release  
release: CXXFLAGS+=$(CXX_RELEASE_FLAGS)
release: HelloWorld

DEBUG_OBJECTS = $(addprefix $(PROJECT_ROOT_DIR)/debug_obj/,$(SOURCES:.cpp=.o))
RELEASE_OBJECTS = $(addprefix $(PROJECT_ROOT_DIR)/release_obj/,$(SOURCES:.cpp=.o))

HelloWorldD: $(DEBUG_OBJECTS)
HelloWorld: $(RELEASE_OBJECTS)

# And let's add three lines just to ensure that the flags will be correct in case
# someone tries to make an object without going through "debug" or "release":

CXX_BASE_FLAGS=-Wall -Wextra -Werror -DLINUX
$(DEBUG_OBJECTS): CXXFLAGS=$(CXX_BASE_FLAGS) $(CXX_DEBUG_FLAGS)
$(RELEASE_OBJECTS): CXXFLAGS=$(CXX_BASE_FLAGS) $(CXX_RELEASE_FLAGS)
Related Topic