0
votes

I have a makefile for a project that I want to be used for debug and release builds. For the debug build I have to include an extra cpp file that holds all the unit tests. I have added a debug option to the makefile and everything seems to work except for one line.

My makefile is pretty long, but the offending line is in this block:

debug: CXXFLAGS+=-DDEBUG_TEST
debug: CXXFLAGS+=-DTEST_DIRECTORY='"$(subst /makefile,,$(abspath $(lastword $(MAKEFILE_LIST))))/tests"'
debug: SRCS+=src/test.cpp
debug: flex bison $(SRCS) $(EXE)

The first two lines that add to CXXFLAGS are parsed successfully, as well as the last line which does the compilation. However, it seems that no matter what I do SRCS never gets the test file appended to it. I have even printed the contents before and after and it shows no change. What am I doing wrong?

I am using GNU Make 3.81 on windows.

Edit: I've decided to add the entire makefile to help answering.

CXX=g++
CXXFLAGS+=-std=c++11 -U__STRICT_ANSI__
FLEX=win_flex.exe
BISON=win_bison.exe
FLEXBISONSRCS=src/parser.cpp src/tokens.cpp src/math_parser.cpp src/math_tokens.cpp
SRCS=$(FLEXBISONSRCS) src/main.cpp src/assembler.cpp src/instruction.cpp src/util.cpp
OBJS=$(SRCS:.cpp=.o)
EXE=bin/yasa

debug: CXXFLAGS+=-DDEBUG_TEST
debug: CXXFLAGS+=-DTEST_DIRECTORY='"$(subst /makefile,,$(abspath $(lastword $(MAKEFILE_LIST))))/tests"'
debug: SRCS+=src/test.cpp

all debug: flex bison $(SRCS) $(EXE)
    @echo ' $$^: $^'
    @echo ' $$(SRCS): $(SRCS)'

$(EXE): $(OBJS)
    $(CXX) $(OBJS) -o $@

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

flex:
    $(FLEX) -o src/tokens.cpp src/65c816.l
    $(FLEX) -o src/math_tokens.cpp src/math.l

bison:
    $(BISON) -d -o src/parser.cpp src/65c816.y
    $(BISON) -d -o src/math_parser.cpp src/math.y

rebuild: clean all

clean:
    rm src/*.o
1
How are you testing this? That assignment is only in effect for that target and any targets that are pre-reqs of that target. (More specifically it is not in effect for the prerequisites of the target itself as you are trying there. So use an intermediate phony debug target instead of having debug have the build recipe (or live with the fact that changes to that test file won't trigger a make rebuild of the debug binary).Etan Reisner
If by testing you mean invoking, then I use regular "make" when doing a release build, "make debug" when doing a debug build, and "make clean" or "make rebuild" when I switch from one to the other. If that's not what you meant then please clarify.ozdrgnaDiies
I meant how are you testing that the assignment isn't doing anything. It is absolutely changing the value of SRCS but the scope of that change is limited. See gist.github.com/deryni/897ff4584abc34fa8851 .Etan Reisner
Oh, I see now. I am using $(info $$SRCS is [${SRCS}]) to print out the value. The output doesn't change after the assignment and test.cpp never gets compiled even on a clean build.ozdrgnaDiies
Right, a toplevel $(info) call won't see it. The assignment is scoped such that that can't function. What is the body of the debug target? It must be doing the compilation itself (as you have the .c files and not object files listed as prerequisites). I'm assuming it probably uses $^ in the recipe? Using $(SRCS) here would "fix" the problem but ultimately the simpler solution is to make the assignment global based on target being built and not target-specific. I'll write up an answer.Etan Reisner

1 Answers

1
votes

The scope of target-specific variable assignments is limited to the recipe of that target and the recipes of any prerequisites of that target.

Specifically that scope does not extend to the prerequisite list itself.

So by way of example this makefile:

SRCS := a.c b.c c.c

debug: SRCS+=d.c

all debug: $(SRCS)
        @echo ' $$^: $^'
        @echo ' $$(SRCS): $(SRCS)'

generates this output when run:

$ make
 $^: a.c b.c c.c
 $(SRCS): a.c b.c c.c
$ make debug
 $^: a.c b.c c.c
 $(SRCS): a.c b.c c.c d.c

You can read all about how make reads makefiles and about when variables are expanded in different parts of the makefile in the How make Reads a Makefile section of the manual.

As indicated this means that using $(SRCS) in the debug target's recipe would work here (as it has the correct value) but using the automatic variables does not but that's not exactly a good (or necessarily even possible) solution.

Another way to do this would be to make the assignment global but only do it when debug was the target being built. Something like this:

ifneq (,$(filter debug,$(MAKECMDGOALS)))
    SRCS += src/test.cpp
endif

This won't let you run make all debug to build both (for example) but will work otherwise.

A few general comments on your makefile. You would do better than those flex and bison targets by teaching make how to run those commands on those pairs of input and output and just leaving it to do that.

Something like this:

src/tokens.cpp: src/65c816.l
    $(FLEX) -o $@ $^

src/math_tokens.cpp: src/math.l
    $(FLEX) -o $@ $^

src/parser.cpp: src/65c816.y
    $(BISON) -d -o $@ $^

src/math_parser.cpp: src/math.y
    $(BISON) -d -o $@ $^

and removing flex and bison from the all and debug prerequisites. No need to add anything else, make already knows it needs those .cpp files and now it knows how to generate them from sources so it will if it needs to.

Additionally all and debug don't want to depend on $(SRCS) and they don't need to. (They already do transitively through $(EXE) and $(OBJS).

None of that solves the problem though. Unfortunately, there isn't a simple (good) solution to this problem. make doesn't support dynamic prerequisites this way.

The solution above about a conditional addition to SRCS is the closest solution I think you have here (and should work just fine as long as that happens before the OBJ assignment).

Actually, a slightly better solution might be to do something like this:

DEBUG_OBJS=$(SRCS:.cpp=.o) src/test.o

DEBUG_EXE := bin/yasa

debug: $(DEBUG_EXE)

$(DEBUG_EXE): $(DEBUG_OBJS)
    $(CXX) $^ -o $@