This is the prequel to the gmake thread. These articles first appeared in the ARSC
HPC Newsletter.
Introduction to Make
Make is a tool which is almost as old as Unix, designed primarily for keeping track of how programs are compiled. That is what we will describe here, although it can be used for other things, including web page maintenance. It is just a matter of telling
make what commands need to be run to update your files.
Make gets its instructions from a description file, by default named
Makefile. This file is also called the
Makefile, but other files can be used by invoking
make with the
-f option, e.g.:
<TAB> make -f Makefile.yukon
When I first got our ocean model, its
Makefile looked something like:
model: main.o init.o plot.o
<TAB> f90 -o model main.o init.o plot.o
main.o: main.f
<TAB> f90 -c -O main.f
init.o: init.f
<TAB> f90 -c -O init.f
plot.o: plot.f
<TAB> f90 -c -O0 plot.f
clean:
<TAB> rm *.o core
The default thing to build is "
model", the first target. The syntax is
target: dependencies
<TAB> command
<TAB> command
The target
model depends on the object files,
main.o and friends. They have to exist and be up to date before
model's link command can be run. The other targets tell
make how to create the object files. The original version of this
Makefile turned off optimization on plot.f due to a compiler bug, but hopefully you won't ever have to worry about that.
Compiling
model is simple, just type "
make".
Make will look for the file
Makefile, read it, and do whatever is necessary to make
model up to date. If you edit
init.f, that file will be newer than
init.o.
Make would see that
init.o is out of date and run the
f90 -c -O init.f command. Now
init.o is newer than
model, so the link command
f90 -o model main.o init.o plot.o must be executed.
Clean up by typing "
make clean". The
clean target will be brought up to date.
clean has no dependencies, so the command
rm *.o core will always be executed.
Macros
Make supports a simple string substitution macro. Set it with:
MY_MACRO = nothing today
and refer to it with:
$(MY_MACRO)
The convention is to put the macros near the top of your Makefile and to use upper case. Also, use separate macros for the name of your compiler and the flags it needs:
F90 = f90
F90FLAGS = -O3
LIBDIR = /usr/local/lib
LIBS = -L$(LIBDIR) -lmylib
Let's rewrite our Makefile using macros:
#
# IBM version
#
F90 = xlf90
F90FLAGS = -O3 -qstrict
LDFLAGS = -bmaxdata:0x40000000
model: main.o init.o plot.o
<TAB> $(F90) $(LDFLAGS) -o model main.o init.o plot.o
main.o: main.f
<TAB> $(F90) -c $(F90FLAGS) main.f
init.o: init.f
<TAB> $(F90) -c $(F90FLAGS) init.f
plot.o: plot.f
<TAB> $(F90) -c $(F90FLAGS) plot.f
clean:
<TAB> rm *.o core
Now when we change computers, we only have to change the compiler name in one place. Likewise, if we want to try different optimization levels, we only specify that in one place.
By the way, you can use comments by starting the line with a
#
Implicit Rules
Make has some rules already built in. For fortran, you might be able to get away with:
OBJS = main.o init.o plot.o
model: $(OBJS)
<TAB> $(FC) $(LDFLAGS) -o model $(OBJS)
as your whole
Makefile. Make will automatically invoke its default Fortran compiler, possibly
f77or
g77, with whatever default compile options it happens to have (
FFLAGS). One built in rule often looks like:
.c.o:
<TAB> $(CC) $(CFLAGS) -c $<
which says to compile
.c files to
.o files using the compiler
CC and options
CFLAGS. We can write our own suffix rules in this same style. The only thing to watch for is that
make by default has a limited set of file extensions that it knows about. Let's write our
Makefile using a suffix rule:
#
# Cray version
#
F90 = f90
F90FLAGS = -O3
LDFLAGS =
.f.o:
<TAB> $(F90) $(F90FLAGS) -c $<
model: main.o init.o plot.o
<TAB> $(F90) $(LDFLAGS) -o model main.o init.o plot.o
clean:
<TAB> rm *.o core
Dependencies
There may be additional dependencies beyond the
source->object ones. In our little example, all our source files include a file called
commons.h. If
commons.h gets modified to add a new variable, everything must be recompiled.
Make won't know that unless you tell it, using the syntax:
# include dependencies
main.o: commons.h
init.o: commons.h
plot.o: commons.h
Fortran 90 introduces module dependencies as well, but we'll save them for another day.
In conclusion,
make is a very powerful tool. The book to read is "Managing projects with make" by Andrew Oram and Steve Talbott, 1991, O'Reilly.
Make has a portable subset of features, with system-dependent extensions. If you want to use extensions, I suggest sticking with those supported by gnu make (gmake), since it is available most everywhere.
The most common newbie mistake is to forget that the commands after a target *have* to start with a tab.