Basic make

Part 2 of A GNU/Unix development environment primer

The make utility allows one to describe the compilation process concisely in a file named ``Makefile'' located in the same directory as your source code, so that to compile a program named ``target'' all you would type is ``make target'' at the shell prompt. At that point make would bring ``target'' up to date using the description of how in ``Makefile''.

An all-too-common approach to using make is to find somebody else's Makefile and to modify it appropriately to fit with your current project. Unfortunately, this strategy does not allow you to make much use of the utility's capabilities and scales up to larger projects very poorly. This short primer on the utility should describe enough to get you well on the way to writing your own sophisticated Makefiles.

Many other users write shell or Perl scripts to automate the compilation process, citing the non-portability of Makefiles. Though their argument is justified, their solution tends to be more complicated than necessary. Hopefully, the existence of a GNU make will allow the writing of more portable Makefiles. This introduction, though, covers Unix make, since that is what is available locally.

Explicit rules

The most basic of the Makefile rules is the explicit rule, which looks like the following:

prog: foo.cpp bar.cpp prog.h
        g++ -Wall -g -o prog foo.cpp bar.cpp
This rule has three parts. First we have the target, ``prog'', followed by a list of its dependencies, ``foo.cpp'', ``bar.cpp'', and ``prog.h''. These two parts are separated by a colon and are followed by a newline. The next part is a list of shell commands, each indented by a tab.

A target is called out-of-date if one of its dependencies is itself out of date or is newer than (i.e., has been modified more recently than) the target. The process of determining whether a target is out of date, therefore, is complex and recursive. In this example, ``prog'' will be out of date if any of ``foo.cpp'', ``bar.cpp'', or ``prog.h'' is newer than ``prog''. Note that source files such as ``foo.cpp'' have no dependencies, so in this example, since all the dependencies of ``prog'' are source files, make does not need to determine which dependencies are out of date.

If your Makefile includes the example rule above and you type ``make prog'', make will see if ``prog'' is out of date; if it is, it will update any out-of-date dependencies and then execute the shell commands following the rule.

Though this may sound quite complicated, in practice it is easy to understand: if ``foo.cpp'', ``bar.cpp'', or ``prog.h'' have been modified since ``prog'' was last compiled, the statement

        g++ -Wall -g -o prog foo.cpp bar.cpp
will be executed, making ``prog'' newer than all its dependencies. If it was already newer than its dependencies, there is no reason to recompile the program, so make will print a message explaining that there is nothing to compile and terminate.

Let us now look at a more complicated example Makefile (though its end result is the same thing as before):

prog: foo.o bar.o
        g++ -Wall -g -o prog foo.o bar.o

foo.o: foo.cpp prog.h
        g++ -Wall -g -c foo.cpp

bar.o: bar.cpp prog.h
        g++ -Wall -g -c bar.cpp
If you type ``make prog'' now, make will first update the dependencies of ``prog'' (``foo.o'' and ``bar.o'') as necessary. Say that you have only modified ``foo.cpp'' since last making ``prog''. Then ``bar.o'' is still up to date, and only ``foo.o'' will be recompiled. Once ``foo.o'' is brought up to date, then ``prog'' will be brought up to date. So the process will execute the following two commands, if all goes well:
        g++ -Wall -g -c foo.cpp
        g++ -Wall -g -o prog foo.o bar.o
If there is an error in ``foo.cpp'', the process will terminate after the attempted compilation to ``foo.o''. If now you modify ``prog.h'' and then type ``make prog'', both ``foo.o'' and ``bar.o'' will be out of date, so the following command sequence will be executed.
        g++ -Wall -g -c foo.cpp
        g++ -Wall -g -c bar.cpp
        g++ -Wall -g -o prog foo.o bar.o
Note that compile-time errors (but not warnings) will terminate the make process, so if there is a problem with ``bar.cpp'', the final compilation in the above sequence will not occur.

This last example should give you an idea of how useful make can be in working with code spread over several files. We can now see, though, how the process can be made even simpler.

Quick hints

You can save command-line keystrokes by taking advantage of the fact that if make receives no arguments, it will use the first explicit rule in the Makefile. In the previous examples, then, if you type just ``make'', make will update ``prog''.

Also, frequently you want make to regard a source file (say, ``foo.cpp'') as changed when in fact you have not done anything to it. (This might be desirable if you have changed the Makefile rule or you have changed a header file that is not listed as a dependency.) You can quickly do this using the Unix touch command, which changes the modification time on a file without actually modifying the file: ``touch foo.cpp''.

Macros

Makefiles can contain macros which in behavior bear much resemblance to macros in C and C++; that is, their value is inserted using simple textual substitution. Defining a macro is easy: just separate the macro name from its value by an equals sign. For example, your Makefile might read:

CPP = g++
CPPFLAGS = -Wall -g -DDEBUG

prog: foo.cpp bar.cpp
        $(CPP) $(CPPFLAGS) -o prog foo.cpp bar.cpp
As you can see, after a macro MACRO is defined, you refer to it later as $(MACRO). In this example, the statement
        g++ -Wall -g -DDEBUG -o prog foo.cpp bar.cpp
will be executed when you type ``make'' (providing either ``foo.cpp'' or ``bar.cpp'' has changed more recently than ``prog'').

This allows you to modify your Makefile's behavior more easily. If, after you finish debugging, you want to optimize the code for your final version, you need only change the definition of CPPFLAGS to:

CPPFLAGS = -Wall -O
To create your final version, type ``touch *.cpp'' at the shell prompt (so that all source code is newer than the object code and executables) and then type ``make'', which in this example results in the following compilation:
        g++ -Wall -O -o prog foo.cpp bar.cpp

Inference rules and suffixes

For large projects of many files, Makefiles can grow to unwieldy lengths. To deal with this, make allows for inference rules. Inference rules deal with transforming a file and changing the suffix, as happens during compilation into object code. An inference rule looks like this:

.c.o:
        $(CC) $(CFLAGS) -c $<
This rule says that if, as an example, ``foo.o'' does not have an explicit rule, then if ``foo.c'' exists, ``foo.o'' will depend on it and the command to create ``foo.o'' from ``foo.c'' will be (assuming that $(CC) is ``cc'' and $(CFLAGS) is ``-O''):
        cc -c foo.c
This example uses a special macro, $<. This is valid only in inference rules and contains the out-of-date dependencies. (Other special macros are $@, which contains the name of the target, and $*, which contains the target's name minus its suffix (which is the same thing as the target's dependent's name minus its suffix).)

This seems magical, and indeed it nearly is. Using inference rules can bring down the length of a Makefile dramatically.

In fact, it is even more magical than it seems: make has many implicit inference rules, including the one we have already seen, to deal with code written in C, yacc, lex, and other tools. Unfortunately, make does not yet generally include an implicit inference inference rule to deal with C++ files. Let us then look at a Makefile for handling C++ files:

CPP = g++
CPPFLAGS = -Wall -g -DDEBUG

.SUFFIXES: .cpp

.cpp.o:
        $(CPP) $(CPPFLAGS) -c $<
This is much like the previous rule. The only difference is that, since make does not recognize ``.cpp'' as a suffix automatically, the ``.SUFFIXES'' line must be added to tell it about this new suffix.

A Makefile template

We mentioned earlier that many people get by with make by copying other Makefiles as templates and modifying it as necessary. In fact this strategy works for most simple cases. So here is a useful template for a Makefile:

# Define these macros to match with your project's filenames
TARGET = prog
OBJS = foo.o bar.o baz.o
LIBS = -lm
CPP = g++
CPPFLAGS = -Wall -g -DDEBUG

.SUFFIXES: .cpp

$(TARGET): $(OBJS)
        $(CPP) $(CPPFLAGS) -o $@ $(OBJS) $(LIBS)

# `make clean' removes extra files that crop up during development
clean:
        rm $(OBJS) core

.cpp.o:
        $(CPP) $(CPPFLAGS) -c $<
(A comment in a Makefile begins with a hash sign (`#') and continue to the end of the line.) For most programs you write, you will find that you need only alter the macro definitions in the first few lines. You should know enough by now, though, to puzzle out how this works.

Last updated 16 Feb 1996.