HeaderHeader

GNU Make Tutorial

Introduction

Make is a utility that is used to automate the processing of files that depend on each other. Take the example of a small C++ program that numerically solves a set of equations based on some input data. The output could then be plotted using xmgrace. There are a number of files associated with such a program:

Source Files

Machine Generated Files

input-data output-data
program.cpp program

There are non-trivial dependencies between the machine generated files and the source files. They are

  1. program <- program.cpp
  2. output-data <- program & input-data

The first dependency rule is saying that if the file program is older than the file program.cpp then it needs to be remade from program.cpp and so on...

Let's first write a simple Makefile (containing only two lines that are not comments) that just compiles the program:

# Makefile

# All lines starting with '#' in a Makefile are comments.

# The Dependency Rules take the form
# target : dependency1 dependency2...
#        Command(s) to generate target from dependencies
#
#   IMPORTANT: the 'command(s)' **MUST** be indented with a single TAB
#   character (not 5 spaces, not TAB and a space, etc.) or make will get
#   confused. Luckily, if you just tab in emacs it will work.

program : program.cpp
       g++ -o program program.cpp -lm

That's it! This Makefile can be used by simply typing:

$ make
and it will compile the program for you if 'program.cpp' has been modified more recently than 'program'. It will do nothing if 'program' is newer than 'program.cpp'.


Now let's add an additional piece of functionality: Let's have the Makefile automatically generate the output-data too!

# Makefile

# The following line says that output_data *depends on* program and input_data
output_data : program input-data
	./program

program : program.cpp
       g++ -o program program.cpp -lm

Now when you type

$ make
the output_data will automatically be re-generated if it needs to be (that is, if program.cpp or input-data have been changed since the last time that output-data was created). If 'program' needs to be recompiled in order to re-generate the output data, then 'make' will do it automatically!


Using Makefiles is really convenient if you have several .cpp and/or .hpp files that must be combined together into a single executable program. For example:

# Makefile

# Defines a variable so you don't need to type this out multiple times
INCLUDES=-I/usr/local/g2/include

# Defines a variable so you don't need to type this out multiple times
LIBRARIES=-lg2 -L/usr/local/g2/lib -L/usr/X11R6/lib -lX11 -lm

# This defines a variable OBJS so you don't need to type all these files
# multiple times. The .o files are called 'object files'. These are files that
# are compiled, but not yet combined together (the term is 'linked') into an
# excutable program.
OBJS=NewtonRaphson.o Planet.o RungeKutta.o

program : $(OBJS)
       g++ -o program program.cpp $(OBJS) $(LIBRARIES)

# Here is a Makefile trick!
# This syntax tells 'make' how to make ANY .o file from a .cpp file of the
# same name.  The 'gcc -c' command makes a .o file without taking the extra
# step of linking. The $< is a special Makefile variable that means
# 'whatever the dependency is', which in this case expands to the corresponding
# .cpp file.
%.o:%.cpp
        g++ -c $< $(INCLUDES)

Now whenever you need to recompile your program or pieces of it, you just type 'make' and it will make whatever needs to be made.


We can do other sophisticated things too. For instance, the generation of data and the plotting can all be encapsulated in the Makefile in the following way

# Makefile

# Define a Makefile variable 'CPP' denoting the compiler to be used
CPP = g++

# Define a Makefile variable 'CPPFLAGS' denoting the arguments passed
# into the compiler: -g causes the compiler to insert
# debugging info into the executable and -Wall turns on all warnings
CPPFLAGS = -g -Wall

# Define a makefile variable to list the dynamic libraries that the
# executable needs to be linked to:
# -lm means load the math library
LDFLAGS = -lm

# If output data is up to date, plot it.
plot: output-data
	xmgrace output-data

# If output data is not up to date, recreate it by running the program.
output-data : program input-data
       ./program

# Compile the program.
program : program.cpp
       $(CPP) $(CPPFLAGS) -o program program.cpp $(LDFLAGS) 

# The clean target is used to remove all machine generated files 
# and start over from a clean slate. This will prove extremely
# useful. It is an example of a dummy target, as there will never be a
# file named clean. Thus "make clean" will always cause the following
# command to be executed.
clean :
       rm -f program output-data
	  

This Makefile can be used by:

$ make
	  

It will automatically carry out only those steps thats are neccessary to bring currently out-of-date or missing machine generated files up-to-date (if any such steps are necessary), and then it will plot the output data. This allows you to concentrate on debugging your program and finetuning the input data without having to bother with the intermediate steps of compilation, execution and plotting. For information on more advanced use of Makefiles refer to the info pages for make on your Linux system, like so:

$ info make