elena.bdc elena.bdc - 3 months ago 10
C++ Question

Write makefile for .cpp and .hpp c++

I need help to create a proper makefile (supporting incremental compilation) for multiple .hpp and .cpp files.

I've been looking for information about how to create a proper makefile, but I'm not really sure on how to do it.

I have the following files:
2048.cpp, game.cpp, game.hpp, gameutils.cpp, gameutils.hpp, menu.cpp, menu.hpp, saveandback.cpp, saveandback.hpp and tile.hpp

Right now I'm using the following makefile:

all: 2048.cpp tile.hpp menu.hpp menu.cpp gameutils.hpp gameutils.cpp saveandback.hpp saveandback.cpp game.hpp game.cpp
g++ -g -W -Wall --pedantic -DNDEBUG 2048.cpp -o power

clean:
$(RM) power


Thank you for your help.

Answer

Caveat: I haven't used make in a while so I may be a little rusty on POSIX vs. GNU-make specific stuff. There may also be new features released in the last few years that I'm not aware of. Please feel free to give corrections. Also most of this is from memory.

There are a few things missing from your knowledge set here that you can use to create a decent makefile that only re-compiles things when needed:

  • Generic Rules - These can be used to provide a generic rule for building a filename with one suffix from another. E.g. the following defines a rule for creating any *.o from its corresponding *.cpp:

    %.o: %.cpp
        stuff
    

    In POSIX make these rules are actually specified as:

    .cpp.o:
        stuff
    

    I'm using GNU syntax below but you can (and might want to) replace with POSIX syntax (moot if you leave them out and use the implicit rules, though, see below).

  • Automatic Variables

    • $< will expand to the input of the rule.
    • $@ will expand to the target of the rule.
  • Variables - You can declare variables and give them values, e.g.:

    SOURCES=2048.cpp gameutils.cpp saveandback.cpp
    

    Etc.

  • Text Replacement - You can use text replacement functions to replace suffixes, e.g.:

    OBJECTS=$(SOURCES:.cpp=.o)
    

    Will set OBJECTS equal to SOURCES but with the .cpp's changed to .o.

  • Multiple Rules - If multiple rules are specified for the same target, their prerequisites are merged.

  • Phony Targets

Putting that all together you can get a start, leaving out header dependencies for now:

SOURCES=2048.cpp menu.cpp gameutils.cpp saveandback.cpp game.cpp
OBJECTS=$(SOURCES:.cpp=.o)

power: $(OBJECTS)
    g++ $(OBJECTS) -o $@

%.o: %.cpp
    g++ -c $< -o $@

And it's traditional to define an all rule, which is a phony target since there isn't actually a file named "all":

.PHONY: all
SOURCES=2048.cpp menu.cpp gameutils.cpp saveandback.cpp game.cpp
OBJECTS=$(SOURCES:.cpp=.o)

all: power

power: $(OBJECTS)
    g++ $(OBJECTS) -o $@

%.o: %.cpp
    g++ -c $< -o $@

Now, make actually has some default rules already, including one for %.o: %.cpp, and also it has some default variables. So you can reduce the above to this if you'd like (personally I prefer to explicitly specify rules, but that's just me):

.PHONY: all
SOURCES=2048.cpp menu.cpp gameutils.cpp saveandback.cpp game.cpp
OBJECTS=$(SOURCES:.cpp=.o)

all: power

power: $(OBJECTS)
    $(CXX) $(CXXFLAGS) $(OBJECTS) -o $@

Now, as for your headers, keeping in mind the multiple rules thing, you can simply add those prerequisites by hand based on their includes, e.g.:

.PHONY: all
SOURCES=2048.cpp menu.cpp gameutils.cpp saveandback.cpp game.cpp
OBJECTS=$(SOURCES:.cpp=.o)

all: power

power: $(OBJECTS)
    $(CXX) $(CXXFLAGS) $(OBJECTS) -o $@

2048.o: menu.hpp game.hpp
menu.o: menu.hpp 
game.o: game.hpp

And so on. You probably also want a "clean" rule, another phony target, and it doesn't hurt to put your binary name in a variable since you use it in a few places, e.g.:

.PHONY: all clean
SOURCES=2048.cpp menu.cpp gameutils.cpp saveandback.cpp game.cpp
OBJECTS=$(SOURCES:.cpp=.o)
BINARY=power

all: $(BINARY)

clean:
    $(RM) $(BINARY) $(OBJECTS)

$(BINARY): $(OBJECTS)
    $(CXX) $(CXXFLAGS) $(OBJECTS) -o $@

And actually if you pass -MM to gcc it'll generate Makefile dependencies automatically for you, based on the source file's includes. See here for details and an example.