8
votes

Related: Target-specific Variables as Prerequisites in a Makefile

I'm trying to craft a Makefile which uses a target-specific-variable to specify the output directory for the object files and the final executable. The idea is to maintain two separate binary versions, a 'release' version and a 'debug' version with extra debugging information.

My problem is that 'make' does a clean build every time, even if I haven't changed a thing. I'm pretty sure it's because 'make' is evaluating the prerequisites of the target 'corewars' before the variable declaration in the prerequisites for the 'debug' or 'release' target.

The Makefile is presented below.

CXX=g++
LD=g++
LDFLAGS=
CXXFLAGS=-Iinclude -Wall -Wextra
OBJECTS=main.o Machine.o Core.o ProcessQueue.o Instruction.o
OUTPUT_DIR:=Test/

.PHONY: default
.PHONY: all
.PHONY: release
default: release
all: release
release: OUTPUT_DIR:=Release/
release: corewars

.PHONY: debug
debug: CXXFLAGS+=-DDEBUG -g
debug: OUTPUT_DIR:=Debug/
debug: corewars

corewars: $(OUTPUT_DIR) $(addprefix $(OUTPUT_DIR),$(OBJECTS))
    $(LD) -o $(addprefix $(OUTPUT_DIR),corewars) $(addprefix $(OUTPUT_DIR),$(OBJECTS))

Release:
    mkdir -p $@
Debug:
    mkdir -p $@

%.o: %.cpp include/%.h
    $(CXX) -c $(CXXFLAGS) $< -o $(OUTPUT_DIR)$@


.PHONY: clean
clean:
    $(RM) -r Release
    $(RM) -r Debug
2

2 Answers

15
votes

First of all, a non-phony recipe must create a target, $@, not $(OUTPUT_DIR)$@. Also consider converting directory dependencies into order-only prerequisites.

In order to get a proper value of $(OUTPUT_DIR) inside the list of prerequisites, you would have to use secondary expansion, because otherwise, during the primary expansion, the global definition OUTPUT_DIR:=Test/ is used instead of the target-specific one.

Unfortunately, I can't think of a sane way to make it work using target specific variables, without resorting to secondary expansion and vpath magic. Personally I would rather setup the environment first (find out the value of OUTPUT_DIR, etc.) and then re-execute Make with the proper values.

ifndef OUTPUT_DIR

.PHONY: default all release debug

default all: release

release: export OUTPUT_DIR := Release/
debug:   export OUTPUT_DIR := Debug/
debug:   export EXTRA_CXXFLAGS := -DDEBUG -g

release debug:
    @$(MAKE)

else

# ...
CXXFLAGS := -Iinclude -Wall -Wextra $(EXTRA_CXXFLAGS)

PROGRAM := $(OUTPUT_DIR)corewars
OBJECTS := $(addprefix $(OUTPUT_DIR), \
    main.o Machine.o Core.o ProcessQueue.o Instruction.o)

# Default target.
$(PROGRAM): $(OBJECTS) | $(OUTPUT_DIR)
    $(LD) -o $@ $<

$(OUTPUT_DIR)%.o: %.cpp | $(OUTPUT_DIR)
    $(CXX) -c $(CXXFLAGS) $< -o $@

$(OUTPUT_DIR):
    mkdir -p $@

endif # OUTPUT_DIR

The two parts could them be split into separate makefiles, the root (starter) one, and the one that does the real work, to make the whole thing more manageable.

0
votes

Target-specific variables are only available within the context of the recipes of the target and its recursive prerequisites. That is, target-specific variables cannot be used as targets nor prerequisites.

One workaround is the makefile there.