NERSCPowering Scientific Discovery for 50 Years

Introduction to 'Make'

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

 

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 = ftn
FCOPTS = -O2
LD = ftn
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 = ftn is a macro. Everywhere that $(FC) appears in the makefile, it will be replaced with ftn. Here we use "ftn" (the Fortran compiler wrapper on NERSC Cori system) as an example.  It may be replaced by a native Fortran compiler such as ifort, mpiifrot, and gfortran, etc. 

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 = -O2 and placed it in all the ftn command options. The -O2 option to ftn specifies an optimization level. If we need to change the optimization level for the entire code, we can 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 into 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 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

If the .f file has been modified since the .a file was modified, compile the .f file using $(CF) -c.
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).
Remove the .o file.
CF = ftn
LD = ftn
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
        ftn  -c libex.f
** libex   === End of Compilation 1 ===
1501-510  Compilation successful for file libex.f.
        ftn  -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
        ftn  -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
        ftn  -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
        ftn  -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
        ftn  -o libex libex.o operators.a

% ./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