Compiling Programs with Make

Most Unix systems have historically used the make utility to aid in the compiling and linking of programs. Make performs very well in this role, but its syntactical peculiarities can make it difficult for users to understand it. This document describes those aspects of make that are most important to building executables.

Make is essentially a utility for processing files according to a set of procedures specified by a makefile. Make first looks for a file named makefile; if that is not found it searches for Makefile; if neither is present, the file may be specified from the command line via make -f <filename>.

Make creates one or more products called targets. The target is the output of one or more rules. Rules are described by a rigid format:

target: dependencies
<tab>      rule
The tab is essential. If lines run over, the backslash (\) character is used as a line continuation, as for shell scripts. However, if a backslash continuation character is required at the end of a line, no spaces may occur after it.

The dependencies are the files that must be present in order for the target to be created. For example, an executable may require certain object files. The object files may, in turn, depend upon not only their source files, but also upon some include files.

Make processes a target by working from the first rule it encounters. In each case, it determines whether it needs to execute the rule by detecting whether any of the prerequisites have changed since their corresponding targets were last created. As an example, if a particular source file is changed (the most common situation) and make is invoked, make first examines the executable target. It determines that the executable needs the object files, so it checks object files for dependencies. If an object file is now older than its prerequisite, make recompiles the corresponding source file, then uses the new object file to relink the executable.

For most executables, the required rules are those for compilation and linking. As an example, suppose that we wish to build an executable of a Fortran program whose source consists of the files main.f90, sub1.f90, and sub2.f90. The main program in turn require an include file defs.h. To create the executable, we would need two rules. The first rule describes how to link; the second how to compile. The executable will be called prog. As is true for shell scripts, lines beginning with # are comments. Our example makefile is thus:

#=========================================
prog:  main.o sub1.o sub2.o 
       f90 -o prog  main.o sub1.o sub2.o

main.o: main.f90 defs.h
       f90 -c -O main.f90
sub1.o: sub1.f90
       f90 -c -O sub1.f90
sub2.o: sub2.f90
       f90 -c -O sub2.f90
#=========================================
Typing the command make prog results in the creation of the executable.

If another run is desired with different parameters, so defs.h is changed, then make will automatically update main.o and relink, but will not recompile sub1.f90 or sub2.f90 because they do not depend on it.

Suppose that we wish to compile this program on several platforms and/or using different compilers. It would be simpler and more accurate to have a variable for the compiler name, and another for the options desired, so that it would be unnecessary to change most of the lines to switch compilers. Make allows variable substitution. Variables must be defined before they are used; they are referenced by being preceded by a $ and enclosed in parentheses. If a variable is not assigned a value, it is replaced by a blank when it is referenced. This makes it convenient to make changes; for example, a code can be recompiled for debugging by uncommenting a line containing a variable definition. We can now change our makefile to:

#=========================================
FC      = f90
DBG     = 
#DBG    = -g
FFLAGS  = -O $(DBG)

LDR     = f90
LDFLAGS =
                                      
prog: main.o sub1.o sub2.o
        $(LDR) -o prog $(LDFLAGS) main.o sub1.o sub2.o

main.o: main.f90 defs.h
       $(FC) -c $(FFLAGS) main.f90
                                        
sub1.o: sub1.f90
        $(FC) -c $(FFLAGS) sub1.f90
                                        
sub2.o: sub2.f90
        $(FC) -c $(FFLAGS) sub2.f90
#==========================================
The rules for compiling are repetitive; especially for a program with a large number of files, typing a separate rule for compiling each file would be tedious and prone to error. A better solution is to use suffix rules. These tell make how to handle all files with a particular suffix; they are inserted into a makefile with the SUFFIXES keyword. For example:

.SUFFIXES: .o .f90
.f90.o:
        $(FC) -c $(FFLAGS) $<

The form of the target tells make that a .f90 file is a prerequisite for its corresponding .o file. As is the case for other rules, the compilation rule must begin with a tab. The macro $< refers to whatever prerequisite is needed to build the current target. In our example case, when make is creating sub1.o, $< will evaluate as sub1.f90.

Suffix rules are typically used in conjunction with lists of objects and/or sources.

Makefiles can, and usually do, contain more than one target. If make is invoked with no argument, it builds the first target it finds. If another target is desired, the command becomes make <target>. For example, it is common to include a target, often called clean, to remove all object files so that the executable can be rebuilt from scratch.

Our sample makefile then becomes

#=============================================
FC      = f90
DBG     = 
#DBG    = -g
FFLAGS  = -O $(DBG)

LDR     = f90
LDFLAGS =
 
SRCS    = main.f90 sub1.f90 sub2.f90
OBJS    = main.o sub1.o sub2.o
 
.SUFFIXES: .o .f90
.f90.o:
        $(FC) -c $(FFLAGS) $<
 

prog: $(OBJS) 
        $(FC) -o prog $(OBJS)
 
main.o: main.f90 defs.h
        $(FC) -c $(FFLAGS) main.f90
clean:
        rm *.o
#================================================

Makefiles can become considerably more complex than this, but the majority of those used to compile numerical codes are of this basic form.

More detailed tutorials can be found at

GNU Makefile Tutorial
The Makefile

and various other places around the Web. The complete manual for Gnu make, which is standard on Linux systems such as the Aspen and Birch clusters, and is available on most other ITC research platforms as gmake, can be studied at the gnu make Web site. Gnu make provides several powerful extensions to standard make.

© 2009 by the Rector and Visitors of the University of Virginia.

The information contained on the University of Virginia’s Department of Information Technology and Communication (ITC) website is provided as a public service with the understanding that ITC makes no representations or warranties, either expressed or implied, concerning the accuracy, completeness, reliability or suitability of the information, including warrantees of title, non-infringement of copyright or patent rights of others. These pages are expected to represent the University of Virginia community and the State of Virginia in a professional manner in accordance with the University of Virginia’s Computing Policies.