Using Makefiles

Executive Summary: This document is a brief description of the Unix Make facility for building executables. It gives some simple examples of Makefiles for compiling C++ projects.

What is Make?

Why use Makefiles?

Suppose I have a project consisting of two classes String and Stack, The class String uses the Stack class to implement the Reverse() operation. I've specified the interface for these classes and their implementation in the files String.h, Stack.h, and String.C, Stack.C, respectively. In addition, I have a main.C that uses the String class. My project dependencies are then: Ultimately, I'm building an executable revHW. What I would like is an automatic way to compile the .C files into object files (.o), and then have these linked to create revHW. However, I don't want to always type in the g++ command lines every time I need to compile mu program. I could write a shell script to do this for me, however I don't want to have to compile evereything when just one module changes. For example, if I change Stack.C, I need only recompile Stack.o and relink revHW, but none of the other .o's need recompilation. If Stack.h changes, I need only recompile Stack.o and String.o and relink revHW.

The above task can be accomplished with Make. In the same directory as my source create a file called "Makefile" with the contents:

main.o:main.C String.h
     g++ -g -c -o main.o main.C

String.o: String.C String.h Stack.h
     g++ -g -c -o String.o String.C

Stack.o: Stack.C Stack.h
     g++ -g -c -o Stack.o Stack.C

revHW: main.o String.o Stack.o
     g++ -o revHW main.o String.o Stack.o 
All you need to do to build revHW when you make a change to the source is type:
% make revHW
and Make will do the right thing.

More complicated files

The above Makefile is very simple, but for larger projects generating such a Makefile like the above would be cumbersome. Make has some features to save some of your typing. In addition, programmers have devised conventions for writing Makeiles that make it easy for one to copy a previously used Makefile and easily modify it for any new projects. This is illustrated in the Makefile below which accomplishes the same as the above, but is written for more general purpose use:
# some variables describing your program source
# in most cases, you'll only need to edit these four variables
HEADERS = String.h Stack.h
SOURCES = String.C Stack.C main.C
OBJECTS = main.o String.o Stack.o

# some useful others that you may need to edit
CC = g++

# name of this file
MF = makefile

.SUFFIXES: .o .h .C

# ------------- Stuff you shouldn't have to change ------------------

        $(CC) $(CFLAGS) -c -o $*.o $<

        $(CC) -o $(PROGRAM) $(CFLAGS) $(OBJECTS) $(LIBS)

        @rm -f *~ "#*" *.o $(PROJECT)

        @echo 'updating the dependencies for:'
        @echo '    ' $(SOURCES)
        @{ \
        < $(MF) sed -n '1,/^###.*SUDDEN DEATH/p'; \
            echo '#' ; \
            echo '# dependencies generated on: ' `date` ; \
            echo '#' ; \
            for i in $(SOURCES); do \
                $(CC) -MM $(CFLAGS) $(DEFINES) $$i ; \
                echo; \
            done \
        } > $(MF).new
        @mv $(MF) $(MF).last
        @mv $(MF).new $(MF)

# dependencies generated on:  Wed Apr 2 23:23:09 PST 1997
String.o: String.C String.h Stack.h

Stack.o: Stack.C Stack.h

main.o: main.C String.h
As before, you build revHW with "make revHW". In addition, "make clean" will remove the object files, executable, and auto-save files so you, so you can start with a fresh project directory.

Automatically generating dependencies

In the example above, notice the "depend:" directive. The mess of commands below it actually edits your Makefile to add the file dependencies of your project at the bottom of the file. The last few lines of the file were actually generated automatically by the command:
% make depend
You should do this anytime you add new source, or add/remove include dependencies in your source code.