0
votes

In the following Makefile, I'm modifying the content of $(SRC) based on whether the target rule is 'test'. This rule is supposed to be for unit testing.
My objects are built with the implicit rule
OBJ = $(SRC:.cpp=.o)
but when building the object, even if the SRC variable changes, it always uses its default value. Here is the Makefile :

CC =              g++

SRC_DIR =         src
SRC_TEST_DIR =    tests/src

INC_DIR =         include/
test: INC_DIR +=  tests/include/

SRC_MAIN =        $(SRC_DIR)/main.cpp
test: SRC_MAIN =  $(SRC_TEST_DIR)/main.cpp

SRC =             $(SRC_MAIN) \             # <=== The value that changes in SRC
                  $(SRC_DIR)/minicalc.cpp \
                  $(SRC_DIR)/io.cpp

OBJ =             $(SRC:.cpp=.o)

CPPFLAGS +=       -I$(INC_DIR) -g -Wall -Wextra

test: LDFLAGS +=  -lcppunit

OUT =             minicalc
test: OUT =       tests/tests


all: $(OBJ)
# DEBUG :
    @echo SRC: '$(SRC)'
    @echo OBJ: '$(OBJ)'

    $(CC) $(OBJ) -o $(OUT) $(LDFLAGS)

test: all
    ls $(OUT) && ./$(OUT)

Here are the outputs :
make gives expected output with default value of SRC :

g++  -Iinclude/ -g -Wall -Wextra  -c -o src/main.o src/main.cpp
g++  -Iinclude/ -g -Wall -Wextra  -c -o src/minicalc.o src/minicalc.cpp
g++  -Iinclude/ -g -Wall -Wextra  -c -o src/io.o src/io.cpp
SRC: src/main.cpp src/minicalc.cpp src/io.cpp 
OBJ src/main.o src/minicalc.o src/io.o
g++ src/main.o src/minicalc.o src/io.o -o minicalc 

But make test doesn't build test/src/main.o even if it is in SRC :

g++  -Iinclude/ -g -Wall -Wextra  -c -o src/main.o src/main.cpp
g++  -Iinclude/ -g -Wall -Wextra  -c -o src/minicalc.o src/minicalc.cpp
g++  -Iinclude/ -g -Wall -Wextra  -c -o src/io.o src/io.cpp
SRC: tests/src/main.cpp src/minicalc.cpp src/io.cpp 
OBJ: tests/src/main.o src/minicalc.o src/io.o
g++ tests/src/main.o src/minicalc.o src/io.o -o tests/tests -lcppunit
g++: error: tests/src/main.o: No such file or directory
make: *** [Makefile:41: all] Error 1

all requires $(OBJ), but the object created are not corresponding to the content of the $(SRC) variable


Not using the implicit .cpp.o rules by doing it manually WORKS :
Same Makefile but replaced $(OBJ) by the rule create_obj

CC =              g++

SRC_DIR =         src
SRC_TEST_DIR =    tests/src

INC_DIR =         include/
test: INC_DIR +=  tests/include/

SRC_MAIN =        $(SRC_DIR)/main.cpp
test: SRC_MAIN =  $(SRC_TEST_DIR)/main.cpp

SRC =             $(SRC_MAIN) \
                  $(SRC_DIR)/minicalc.cpp \
                  $(SRC_DIR)/io.cpp    

OBJ =             $(SRC:.cpp=.o)

CPPFLAGS +=       -I$(INC_DIR) -g -Wall -Wextra

test: LDFLAGS +=  -lcppunit

OUT =             minicalc
test: OUT =       tests/tests


all: create_obj
# DEBUG :
    @echo   SRC: '$(SRC)'
    @echo   obj: '$(OBJ)'

    $(CC) $(OBJ) -o $(OUT) $(LDFLAGS)

create_obj:
    @echo Crafting Files:
    @$(foreach file, $(SRC), \
        echo  $(file),;  \
        $(CC) -c $(file) -o $(file:.cpp=.o) $(CPPFLAGS) $(LDFLAGS); \
    )

test: all
    ls $(OUT) && ./$(OUT)

Here are the outputs :
make :

Crafting Files:
src/main.cpp,
src/minicalc.cpp,
src/io.cpp,
SRC: src/main.cpp src/minicalc.cpp src/io.cpp 
OBJ: src/main.o src/minicalc.o src/io.o
g++ src/main.o src/minicalc.o src/io.o -o minicalc 

make test

Crafting Files:
tests/src/main.cpp,
src/minicalc.cpp,
src/io.cpp,
SRC: tests/src/main.cpp src/minicalc.cpp src/io.cpp 
OBJ tests/src/main.o src/minicalc.o src/io.o
g++ tests/src/main.o src/minicalc.o src/io.o -o tests/tests -lcppunit
ls tests/tests && ./tests/tests
tests/tests


OK (0)

Why does the implicit building of my objects not change when changing my sources, while doing it manually works ?

2

2 Answers

1
votes

In order for make to initially find all dep relations, it must expand vars in whatever state they are at that point, in the initial scan. So all : $(OBJ) is evaluated with the global value, and not reevaluated in the context of test : all. Sorry. Make's lazy eval does not play well with context-dependent var assignment.

1
votes

At the risk of overkill, here's how I would have written it. Personal guidelines:

  • Use vars sparingly. They have a place but tend to make files less readable.
  • Rely on implicit rules/actions (e.g. %.o: %.cpp; ...) until forced not to.
  • Target-dependent var settings work for vars ONLY used in actions.
  • MAKE really wants to build target and intermediate files in the cwd. Any other scheme bends Make out of joint. That's why you see scripting like: make -C${PLATFORM}/${CONFIG} ...
    
    all         : minicalc            ;@:     # ... or ".PHONY: all"
    test        : mctests             ; ls $^ && ./$^

    minicalc    : minicalc.o  io.o  main.o    # minicalc.o is subtly redundant here.
    mctests     : minicalc.o  io.o  mctests.o # ... likewise mctests.o is redundant.
    mctests.o   : tests/src/main.cpp          # Name difference forces an explicit action below.

    vpath %.cpp src
    CC = ${CXX}                               # Roundabout fix for MAKE using ${CC} for linking
                                              # ... but cc/gcc cannot handle C++ object files.
    CXXFLAGS += -Iinclude -g -Wall -Wextra
    mctests.o   : CXXFLAGS += -Itests/include
    mctests     : LDLIBS   += -lcpptest       # More accurately LDLIBS not LDFLAGS

    mctests.o   :; ${COMPILE.cpp} -o $@ $^