DaedTech

Stories about Software

By

A Small, Functional Makefile

I don’t write C++ all that often these days, but I suppose that I spent so many years with it that it never really feels foreign to me when I come back to it. What does oftentimes feel foreign is generating a Makefile when developing in Linux. I’ve made enough of them over the years that I know what I’m doing but not so many that I can go off the cuff after six months or a year break from them.

So I’m sticking a sample Makefile here. This is somewhat for me to refer back to whenever I need to, but I’ll also explain some of the basics. In this example, I’m creating a little C++ application for calculating the odds of poker hands, given what is on the table at the moment. At the time of writing, the example, in its infancy, has only one class: Card. So the files at play here are card.h, card,cc and main.cpp. The main class references card.cpp, which, in turn, references its class definition header file, card.h.

all: oddscalc

card.o: card.cpp

	g++ -Wall -c -o card.o card.cpp

main.o: main.cpp
	g++ -Wall -c -o main.o main.cpp

oddscalc: card.o main.o

	g++ card.o main.o -o oddscalc

clean: 
	rm -f *.o oddscalc

So there’s the simple Makefile. If you take a look at this, the main purpose of the Makefile is, obviously, to compile the source, but also to automate linking so that, as projects grow, you don’t have increasingly unwieldy g++ command line statements. So we define a few Makefile rules. First, card.o is generated by compiling card.cc. Second, main.o is generated by compiling main.cpp. The executable is generated by linking the two object files, and “all” is the executable.

That’s all well and good, but I can eliminate some duplication and make this more configurable. I’ll use Makefile variables so that I don’t have to repeat things like “card,” “oddscalc,” and “g++” everywhere.

In addition, I can see the inevitable redundancy coming from our previous Makefile. As soon as I add hand.cpp/hand.h and deck.cpp/deck.h, I’m going to have to create rules for them as well. Well, I don’t want to do that, so I’m introducing a scheme that, in essence, says, “compile every .cpp file I give you into a .o file and link it in the main assembly.” This will be expressed with a “.cpp.o” rule.

#Defines
CC=g++
CFLAGS=-c -Wall
EXE=oddscalc
SOURCES=main.cpp card.cpp
OBJECTS=$(SOURCES:.cpp=.o)

#Build Rules

all: $(SOURCES) $(EXE)

.cpp.o:
	$(CC) $(CFLAGS) $< -o $@

$(EXE): $(OBJECTS)

	$(CC) $(OBJECTS) -o $(EXE)

clean: 
	rm -f *.o oddscalc

With this Makefile, if I want to add a new class, all I need to do is add the class's .cpp file to the "SOURCES" definition line and it will get compiled and linked for the application. (Well, obviously, I need to write the class as well, but we're just talking about the Makefile here.)

So that's it. There are a lot of things you can do with Makefiles. Some people create a variety of build configurations. "make tar" is a popular option as well. But I think that this Makefile is relatively simple and elegant, and it's easy to add to.