makefile target specific variables as prerequisites

10,992

Solution 1

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.

Solution 2

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.

Share:
10,992

Related videos on Youtube

Chris Browne
Author by

Chris Browne

typedef enum { FALSE=0, TRUE } boolean; typedef struct { int age; char* name; boolean employed; char* occupation; char* education; } programmer_t; programmer_t chris; chris.age = 31; chris.name = "Chris Browne"; chris.employed = TRUE; chris.occupation = "Chief Architect"; chris.education = "self-taught"; I maintain an active interest in a plethora of programming languages, paradigms, and toolchains. I aspire to be language and tool agnostic. Interests Artificial Intelligence programming/design Low-level high-performance microcoding (asm/c) Robotics User Interface Design Retrocoding (writing programs for old systems using the tools of the time) Programming Language Design/Development Board games Video games

Updated on September 23, 2022

Comments

  • Chris Browne
    Chris Browne over 1 year

    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
    
  • Chris Browne
    Chris Browne almost 12 years
    This is an excellent sanitisation of my build system, thank you. I found it necessary to add | $(OUTPUT_DIR) to the prerequisite list for the rule to make .o files, and I added a 'clean' target, but apart from that this works as quoted. Many thanks.
  • Eldar Abusalimov
    Eldar Abusalimov almost 12 years
    @Chris, oh, yes, you're right, I forgot about it (fixed). And you're welcome! ;-)
  • Chris Browne
    Chris Browne almost 12 years
    Thanks for the heads up! I'll fix that now.