NERSC logo National Energy Research Scientific Computing Center
  A DOE Office of Science User Facility
  at Lawrence Berkeley National Laboratory
Restore navigation column
NERSC Tutorials

Introduction to make

The UNIX make utility facilitates the creation and maintenance of executable programs from source code. This tutorial will introduce the simple usage of the make utility with the goal of building an executable program from a series of source code files.

Introduction

The UNIX make utility facilitates the creation and maintenance of executable programs from source code. make keeps track of the commands needed to build the code and when changes are made to a source file, recompiles only the necessary files. make creates and updates programs with a minimum of effort.

A small initial investment of time is needed to set up make for a given software project, but afterward, recompiling and linking is done consistently and quickly by typing one command: make, instead of issuing many complicated command lines that invoke the compiler and linker.

This tutorial will introduce the simple usage of the make utility with the goal of building an executable program from a series of source code files. Most of the varied, subtle, and complex features of make are the subject of entire books and are not covered here.

This tutorial assumes that you have some familiarity with UNIX, text editors and compiling programs from source code.

A project

We'll illustrate the use and usefulness of make with a little project. This project does nothing more than print out the current version of the program.

This project has three source code files

project.f
The main program, which calls a subroutine, print_project_info
print_project_info.f
Contains the subroutine print_project_info
project_info.f
Contains parameter definitions in a Fortran 90 module that are used by the subroutine print_project_info
!	FILENAME: project.f
!
PROGRAM project

	IMPLICIT NONE

	CALL print_project_info

	STOP
	
END PROGRAM project

!	FILENAME: print_project_info.f
!
SUBROUTINE print_project_info

	USE project_info
	IMPLICIT NONE

	PRINT *,"This is version",MAJOR_VERSION,".",MINOR_VERSION

END SUBROUTINE print_project_info

!	FILENAME: project_info.f
!
MODULE project_info 

	IMPLICIT NONE
	SAVE

	INTEGER, PARAMETER:: MAJOR_VERSION=0, MINOR_VERSION=2

END MODULE project_info 

Simple make

We'll use make in its most simple forms to produce an executable program from these source code files.

Simple compile

In order to create an executable program, all these source code files need to be compiled and linked. This command line will do it:

% xlf90 -o project project_info.f print_project_info.f project.f 

That's a lot of typing even for this simple program. make will save you from having to remember and type the command line each time.

The make utility gets its instructions from a text file, by default named makefile or Makefile in the current directory.

Here is possibly the simplest and clearest makefile that will perform the above compile and link:

#
# A first makefile

project : project_info.f print_project_info.f project.f
	xlf90 -o project project_info.f print_project_info.f project.f

The pound character, #, in a makefile indicates a comment line. A backslash character indicates that the line is continued on the next line. If we save these lines in a file named makefile, typing make at the command line will compile the program just as if we'd typed in the entire f90 command:

%  make 
project_info.f:
** project_info   === End of Compilation 1 ===
1501-510  Compilation successful for file project_info.f.
print_project_info.f:
** print_project_info   === End of Compilation 1 ===
1501-510  Compilation successful for file print_project_info.f.
project.f:
** project   === End of Compilation 1 ===
1501-510  Compilation successful for file project.f.

In addition, if we don't change any files and type make again:

%  make 
Target "project" is up to date.

make knows that the file does not need to be recompiled. However, if we change one of the source code files, make will recompile the program.

How does it know how to do this? Let's look at the makefile. The first line,

 
project : project_info.f print_project_info.f project.f

contains the program name, project, to the left of a colon. The word or label to the left of the colon is called the target. The files that appear to the right of the colon are called dependencies. The dependencies are those files that are needed to produce the target. So this line is telling make that all the source code files are needed to produce the executable program.

make checks the dependency list and if any of the files have been changed more recently than the target, make executes the next line in the makefile:

    xlf90 -o project project_info.f print_project_info.f project.f

If the target is more recent than the dependency, make will tell you so and skip the command.

NOTE: Although you can't see it, there is a TAB character before the xlf90 command. This TAB must be present for make to work. All the blank spaces in the world will not help if there is no TAB present and make will give you little clue as to why it is failing.

Better compile

In the previous example, every source code file was recompiled every time a new executable was produced. This is not necessary in general, and it could waste a lot of time. If we can understand the steps xlf90 takes to build the program, we can instruct make to work in the most efficient manner.

The xlf90 compiling system works in two stages; it:

  1. compiles each .f file into an object file ending with a .o extension
  2. links all the .o files and system library files into the executable program

All that is needed to produce the final program are .o files. The .f files are needed to create .o files. (A possible subtlety regarding xlf90: if a .f file USEs a module contained in a different .f file, it reads the necessary information from the module's .mod file, which must exist.)

Here are the dependencies for this program:

This file's creation depends on these files
project_info.o project_info.f
print_project_info.o project_info.o print_project_info.f
project.o project.f
project project.o print_project_info.o

Here's the makefile that incorporates all these dependencies:

project :  project.o print_project_info.o
        xlf90 -o project project.o print_project_info.o

project_info.o : project_info.f
        xlf90 -c project_info.f

print_project_info.o : print_project_info.f project_info.o
        xlf90 -c project_info.o print_project_info.f

project.o : project.f
        xlf90 -c project.f

The xlf90 -c command tells the compiler to produce the corresponding .o file without trying to link it into an executable.

Now let's make the program:

%  make 
        xlf90 -c project.f
** project   === End of Compilation 1 ===
1501-510  Compilation successful for file project.f.
        xlf90 -c project_info.f
** project_info   === End of Compilation 1 ===
1501-510  Compilation successful for file project_info.f.
        xlf90 -c project_info.o print_project_info.f
** print_project_info   === End of Compilation 1 ===
1501-510  Compilation successful for file print_project_info.f.
        xlf90 -o project project.o print_project_info.o

Typing make again produces:

 % make 
Target "project" is up to date.

Running the program on the NERSC SP yields

%  ./project 
 This is PROJECT version 0 . 2

Now let's change the version to 0.3. After we have edited the file project_info.f, we rebuild the program:

% make 
        xlf90 -c project_info.f
** project_info   === End of Compilation 1 ===
1501-510  Compilation successful for file project_info.f.
        xlf90 -c project_info.o print_project_info.f
** print_project_info   === End of Compilation 1 ===
1501-510  Compilation successful for file print_project_info.f.
        xlf90 -o project project.o print_project_info.o
% ./project 
 This is PROJECT version 0 . 3

Note that make only executed the steps that were necessary to build the program. In particular, project.f was not recompiled. print_project_info.o was recreated because it contains information that is defined in project_info.f.

A bit more

The material presented so far only scratches the surface of the features of the make utility. make has many default and implicit rules for how to build objects from source code. These additional features and rules make make very powerful. However, the rules are often not obvious and can vary from platform to platform, which can make make frustrating and difficult to use. Books devoted to make are available for those who are interested in learning all the details.

In this section, we'll discuss a few additional features of make. All we will do is slightly rework the previous makefile.

Here's a makefile that maintains the same program we've been discussing. The modifications are described below.

FC = xlf90
FCOPTS = -O3
LD = xlff90
LDOPTS =
EXENAME = project
OBJS = project.o print_project_info.o

$(EXENAME) : $(OBJS)
        $(LD) $(LDOPTS) -o $(EXENAME) $(OBJS)


print_project_info.o : print_project_info.f project_info.o
        $(FC) $(FCOPTS) -c project_info.o print_project_info.f

.f.o :
        $(FC) $(FCOPTS) -c $<

clean:
        rm -f core *.o

clobber: clean
        rm -f $(EXENAME)

Macros

You can define macros, or symbols that stand for other things, in makefiles. The lines at the beginning of the makefile are macros. For example FC = xlf90 is a macro. Everywhere that $(FC) appears in the makefile, it will be replaced with xlf90.

These macro definitions are an easy way to change items that appear in many places in the makefile. Notice that we defined the macro FCOPTS = -O3 and placed it in all the xlf90 command options. The -O3 option to xlf90 specifies an optimization level. If we to change the optimization level for the entire code, we just change the one line in which FCOPTS is defined.

Implicit suffix rules

You can give make a set of rules for creating files with a certain suffix from files with the same root file name, but a different suffix. For example, the line

.f.o :

tells make that all .o files are created from the corresponding .f files. The command in the next line will recompile any .f file if it is newer than the corresponding .o file.

        $(FC) $(FCOPTS) -c $<

This line uses the make internal macro $<, which translates to "any dependency that is more recent than its corresponding target." This internal macro can only be used in suffix rules.

Exceptions to the suffix rule can be stated explicitly, as is done here for the print_project_info.o object, which needs the project_info.o module information.

Additional targets

We've added two useful targets in this makefile: clean and clobber. If you type make at the command line, make makes the first target it encounters in the makefile, in this case the executable program. If you type make with an argument, make jumps to the target with the name of the command line argument.

For example, make clean jumps to the clean target. The two new targets perform these tasks

clean
Since the file "clean" does not exist, make clean will always execute the command rm -f *.o, which deletes the .o files. This is useful if you want to get rid of all those files after you're finished building your program.
clobber
make clobber will get rid of the executable file as well as the .o files. The file clobber also does not exist so make clobber will always cause the executable to be removed, and since it depends on clean, it will also imply make clean.

Example: making a library archive

In this example a number of subroutines are incorporated info a library archive. Routines from the library are linked into the main program during the linking process.

It is often convenient to maintain object files in a library. By doing so, you can reduce the number of .o files you need to keep in a directory, which can have many advantages.

The example project

Let's say we have four routines that we want to maintain in a library and a main program (libex.f90) that will call those routines. In this example, the routines simply perform an operation on two real numbers and print the result. We'll make a library file, named operators.a, that will contain the subroutines. After we create the library file, we will delete the object files (*.o).

Here's what the files look like:

% ls
add.f90        libex.f90      multiply.f90
divide.f90     makefile       subtract.f90

! FILENAME: add.f

SUBROUTINE add(x,y)
        IMPLICIT NONE
        REAL, INTENT(IN) :: x, y

        PRINT *, "The sum of", x, " and ", y, "is ", x+y

        RETURN
END SUBROUTINE add

!FILENAME: divide.f

SUBROUTINE divide(x,y)
        IMPLICIT NONE
        REAL, INTENT(IN) :: x, y

        IF(y==0.) THEN
                PRINT *, "Can not divide by zero!"
                RETURN
        END IF

        PRINT *, x, "divided by", y, "is ", x/y

        RETURN
END SUBROUTINE divide 

!FILENAME: multiply.f

SUBROUTINE multiply(x,y)
        IMPLICIT NONE
        REAL, INTENT(IN) :: x, y

        PRINT *, "The product of", x, " and ", y, "is ", x*y

        RETURN
END SUBROUTINE multiply 

!FILENAME: subtract.f

SUBROUTINE  subtract(x,y)
        IMPLICIT NONE
        REAL, INTENT(IN) :: x, y

        PRINT *, "The difference, ", x, " - ", y, "is ", x-y

        RETURN
END SUBROUTINE subtract 

!FILENAME: libex.f
! An example program that illustrates calling routines
! from a user-built library of object modules.

PROGRAM libex 
        IMPLICIT NONE

        REAL:: a=2.0,b=3.0

        CALL add(a,b)
        CALL multiply(a,b)
        CALL subtract(a,b)
        CALL divide(a,b)

END PROGRAM libex

The makefile

The following simple makefile can be used to maintain the archive and produce the executable. make has a special syntax for dealing with libraries. The library is defined on the line

$(LIB) : $(LIB)(add.o) $(LIB)(multiply.o) \
		$(LIB)(divide.o) $(LIB)(subtract.o)

The the rule for producing the library is the following (note that the .o files are removed immediately after updating the library).

.f90.a : 
         $(CF) -c $<
         $(AR) -r $@ $*.o
         /bin/rm -f $*.o

This rule says

  1. If the .f file has been modified since the .a file was modified, compile the .f file using $(CF) -c.
  2. Take the corresponding .o file and replace it in the proper library (as defined above) using $(AR) (The ar command builds and maintains libraries, see man ar).
  3. Remove the .o file.
CF = xlf90 
LD = xlf90 
LIBOBJS = add.o multiply.o divide.o subtract.o
LIB = operators.a
EXE = libex
OBJS = libex.o
AR = ar 


$(EXE): $(OBJS) $(LIB) 
        $(CF) -o $(EXE) $(OBJS) $(LIB) 

$(LIB) : $(LIB)(add.o) $(LIB)(multiply.o) \
		$(LIB)(divide.o) $(LIB)(subtract.o)
        
.f.a : 
        $(CF) -c $<
        $(AR) -r $@ $*.o
        /bin/rm -f $*.o
        
.f.o : 
        $(CF) -c $<


clean :
        /bin/rm  $(OBJS) 

clobber :
        /bin/rm $(EXE) $(LIB) $(OBJS) 

The Output

% make
        xlf90  -c libex.f
** libex   === End of Compilation 1 ===
1501-510  Compilation successful for file libex.f.
        xlf90  -c add.f
** add   === End of Compilation 1 ===
1501-510  Compilation successful for file add.f.
        ar -r operators.a add.o
ar: Creating an archive file operators.a.
        /bin/rm -f add.o
        xlf90  -c multiply.f
** multiply   === End of Compilation 1 ===
1501-510  Compilation successful for file multiply.f.
        ar -r operators.a multiply.o
        /bin/rm -f multiply.o
        xlf90  -c divide.f
** divide   === End of Compilation 1 ===
1501-510  Compilation successful for file divide.f.
        ar -r operators.a divide.o
        /bin/rm -f divide.o
        xlf90  -c subtract.f
** subtract   === End of Compilation 1 ===
1501-510  Compilation successful for file subtract.f.
        ar -r operators.a subtract.o
        /bin/rm -f subtract.o
        xlf90  -o libex libex.o operators.a 

s00505% ./libex
 The sum of 2.000000000  and  3.000000000 is  5.000000000
 The product of 2.000000000  and  3.000000000 is  6.000000000
 The difference,  2.000000000  -  3.000000000 is  -1.000000000
 2.000000000 divided by 3.000000000 is  0.6666666865


LBNL Home
Page last modified: Mon, 11 Jan 2010 21:29:38 GMT
Page URL: http://www.nersc.gov/nusers/help/tutorials/make/print.php
Web contact: webmaster@nersc.gov
Computing questions: consult@nersc.gov

Privacy and Security Notice
DOE Office of Science