Makefile Tutorial

make is a standard UNIX program for building a file from others. It reads a file, usually called Makefile, for a set of rules that describe how to proceed. This page provides a quick introduction to make, and Makefile syntax.

Applications

make can be used for many applications. Some common uses are: For example, the Linux kernel, which consists of 100+ megabytes of source code, is configured and built using make.

Running make

make is typically installed alongside a C compiler, such as gcc. If it is invoked without any arguments, it will look for a file called Makefile in the current directory and execute the first rule it finds there.

Alternate Makefiles can be specified using -f as follows:
make -f Makefile.new
Other targets within a Makefile can be specified by listing them on the command line:
make clean
make prog1

Makefile Syntax

make expects a Makefile that specifies how it will proceed. A Makefile is a collection of rules stored as text. There are three important parts to each rule: Each rule occupies two lines that must be formatted as follows:
target: dependency1 dependency2
	command
Note that the second line of the rule starts with a single TAB character. Spaces cannot be used instead. If a TAB character is not used at the beginning of the second line of a rule, then make will fail.

make works by executing any rules that it requires to build the target, regardless of the order in which they appear in the Makefile. When it attempts to build a target, it does the following:

  1. Checks each of the dependencies for the target
  2. For each dependency, it checks whether a file of that name exists in the current directory.
    • If the file exists, make checks the file's last modification time.
    • If the file does not exist, make looks for another target of that name in the Makefile and recurses, building that target first.
  3. Once each of the dependencies is satisfied (the files were found, or the subtargets were built), make checks whether target already exists.
    • If so, then make checks if it is newer than its dependencies. If so, make stops because the file is up to date and does not need to be rebuilt.
  4. Otherwise, make runs the command specified for the rule.
Note that if a target has no dependencies, its command will be executed anyway. A typical use for this feature is to make macros for a project, such as this rule:
clean:
	/bin/rm -f *.o
This deletes intermediate object (.o) files, and allows the user to clean things up by running make clean.

Example 1

Suppose that a student is compiling a program, hw1.c, for their homework. They create a Makefile that contains the following:
hw1: hw1.c
	gcc hw1.c -o hw1
Now, any time that they want to compile hw1, they simply type make at the command prompt:
[student@localhost tutorial]$ make
gcc hw1.c -o hw1
[student@localhost tutorial]$
While this may look simple, make did the following:
  1. Looked for a file called Makefile in the current directory
  2. Located the first rule, hw1, because no targets were specified on the command line
  3. Checked whether hw1 already existed, and if so, when it was last modified
  4. Looked for the dependencies for hw1 and found hw1.c
  5. Checked whether hw1.c existed, and if so, when it was last modified.
  6. Once it had checked the dependencies for hw1, it determined that hw1.c was newer than hw1 and it ran the command gcc hw1.c -o hw1
Note what happens if make is run again without any modification to hw1.c:
[student@localhost tutorial]$ make
make: `hw1' is up to date.
[student@localhost tutorial]$
That is, make found that hw1's dependencies hadn't changed, so hw1 did not need to be rebuilt.

Example 2

Suppose that the student is trying to build a program that is spread across multiple C++ classes, each of which has a .h file and a .cpp file. The student creates a Makefile that contains the following. Note that any line beginning with a pound sign (#) is a comment, and is ignored by make.
# Primary target: hw2
hw2: main.o Player.o Board.o
	g++ main.o Player.o Board.o -o hw2

# main program that plays a game
main.o: main.cpp
	g++ -c main.cpp

# Class describing a player
Player.o: Player.cpp Player.h
	g++ -c Player.cpp

# Class describing the game board
Board.o: Board.cpp Board.h
	g++ -c Board.cpp
Here is how it looks when the student runs make:
[student@localhost tutorial]$ make
g++ -c main.cpp
g++ -c Player.cpp
g++ -c Board.cpp
g++ main.o Player.o Board.o -o hw2
[student@localhost tutorial]$
When the student runs make, it finds the first target (hw2), then checks whether the object files main.o, Player.o, and Board.o exist. Since they do not, make recurses: it makes main.o its new target and goes about building it (just like in Example 1), then does the same with Player.o and Board.o. Once these dependencies are satisfied, it runs the command to build hw2.

Suppose that the student identifies a bug in the code, and edits Board.h. When they run make again, here is the result:

[student@localhost tutorial]$ make
g++ -c Board.cpp
g++ main.o Player.o Board.o -o hw2
[student@localhost tutorial]$
That is, make did not recompile main.cpp or Player.cpp because their intermediate .o files were still up to date. make did the minimum amount of work necessary to rebuild hw2.

Example 3

This example shows dependencies that are not files, but rather, are other targets in the Makefile. Suppose that the student is keeping all of their homework for the semester in the same directory, but only wants to have one Makefile for all of them. We can combine the previous two previous examples into one Makefile:
# Rule to build all targets
all: hw1 hw2

# Homework 1: hw1
hw1: hw1.c
	gcc hw1.c -o hw1

# Homework 2: hw2
hw2: main.o Player.o Board.o
	g++ main.o Player.o Board.o -o hw2

# main program that plays a game
main.o: main.cpp
	g++ -c main.cpp

# Class describing a player
Player.o: Player.cpp Player.h
	g++ -c Player.cpp

# Class describing the game board
Board.o: Board.cpp Board.h
	g++ -c Board.cpp
The student can now build a particular program by name:
[student@localhost tutorial]$ make hw1
gcc hw1.c -o hw1
[student@localhost tutorial]$
...or all targets at once:
[student@localhost tutorial]$ make
gcc hw1.c -o hw1
g++ -c main.cpp
g++ -c Player.cpp
g++ -c Board.cpp
g++ main.o Player.o Board.o -o hw2
Note that make all would have had the same effect above; the all target was the default because it was found first.

Advanced Usage

Environment Variables

make can use variables to store commonly used strings. To specify a variable CFLAGS that contains compiler options, for instance, do this:
CFLAGS=-Wall -Wunused -ggdb3
These flags tell gcc to turn on all standard warnings, plus warnings for unused variables, and to store debugging information for gdb. The variable can be expanded as follows:
hw1: hw1.c
	gcc ${CFLAGS} hw1.c -o hw1
This produces the following when executed:
[student@localhost tutorial]$ make hw1
gcc -Wall -Wunused -ggdb3 hw1.c -o hw1
hw1.c: In function `c':
hw1.c:25: warning: format argument is not a pointer (arg 2)
[student@localhost tutorial]$
Note that all such variables must be defined before they are referenced. Also, note that if a variable is not defined in the Makefile, then make will look for an environment variable of that name from your shell. This can be useful, as ${HOME} will expand to your home directory, which can be used to refer to files relative to any user's home.

Guessing about targets

make can guess how to build files based on their names. Usually, a .o file is built using gcc or g++ from a .c or .cpp file of the same name, and make will try to make up rules when it doesn't find what it needs. For instance, the Makefile for Example 2 can be reduced to one rule:
hw2: main.o Player.o Board.o
        g++ main.o Player.o Board.o -o hw2
The result is this:
[student@localhost tutorial]$ make
g++    -c -o main.o main.cpp
g++    -c -o Player.o Player.cpp
g++    -c -o Board.o Board.cpp
g++ main.o Player.o Board.o -o hw2
[student@localhost tutorial]$
In other words, make successfully guessed how to build each of hw2's dependencies. However, it could have guessed wrong, and probably didn't check whether the .h files had changed, so use this feature sparingly. For more about make and how to make it even more useful, see the
complete manual.
This page written by Aaron Gage, Thu Jan 20 17:08:56 EST 2005