Makefiles

We use makefiles to organize code compilation and execution. This page provides a brief tutorial to create and work with makefiles for small to medium-sized Fortran 90/95 projects on Unix/Linux OS. I will introduce you to makefiles through simple fortran examples. With some modifications, you can use makefiles for other programming languages, such as C/C++, provided their compilers can be run with a shell command. It is to be noted that “make” is not limited to manage the execution of programs. It can be used to do other tasks/projects involving multiple files, where some files must be updated automatically whenever we modify other files. As an example, I use “make html” to generate these course webpages.

Let us start off with the following three Fortran source files, consisting of one main file and two subroutines:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
! Main program: main.f90
program main
  implicit none
  real(kind=8) :: a,b,c

  a = 1.d0
  call sub1(a,b)
  call sub2(a,b,c)
  print *, c
  
end program main 
1
2
3
4
5
6
subroutine sub1(a,b)
  implicit none
  real(kind = 8), intent(in)  :: a
  real(kind = 8), intent(out) :: b
  b =  exp(a)
end subroutine sub1
1
2
3
4
5
6
subroutine sub2(a,b,c)
  implicit none
  real(kind = 8), intent(in)  :: a,b
  real(kind = 8), intent(out) :: c
  c=a+b
end subroutine sub2

The goal is to create a makefile that

  • compiles, links, and executes these three source codes;
  • makes necessary updates to some or all files if some other files are modified;
  • and pehaps does some other useful things, such as graphing.

In a Fortran program, the executable file is updated from object files, which are in turn made by compiling source files. Once we create a makefile, each time we change/modify some source files, we can use the following shell command to execute the makefile and perform all necessary recompilations and updates:

$ make -f Makefile-name

where Makefile-name stands for the name of the makefile file that we want to use. The make program uses the latest version of file modifications to decide which of the files in the makefile need to be updated. For example, if a subroutine file has changed, each Fortran source file that calls this subroutine file must be recompiled as well.

In what follows we consider and study several makefiles that get successively more sophisticated to compile, link, and execute these three Fortran codes.

Note: You can find the three source files (main.f90, sub1.f90, sub2.f90) and the follwoing makefiles in the course repository in “Labs/lab2” directory.

Makefile1

In the first version we write out explicitly what to do for each file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Makefile1

output.txt: code.x
	./code.x > output.txt

code.x: main.o sub1.o sub2.o
	gfortran main.o sub1.o sub2.o -o code.x

main.o: main.f90
	gfortran -c main.f90
sub1.o: sub1.f90
	gfortran -c sub1.f90
sub2.o: sub2.f90
	gfortran -c sub2.f90

Notes:

  • A makefile consists of a few “rules” with the following structure:

    TARGET : PREREQUISITE1 PREREQUISITE2 ...
          COMMAND1
          COMMAND2
          ...
    

    A “target” is usually the name of a file that is generated by a program; examples of targets are executable or object files (as in this makefile). A target can also be the name of an action to carry out, such as “clean” or “graph” (see bellow).

    A “prerequisite” is a file that is used as input to create the target. A target often depends on several files.

    A “command” is an action that make carries out. A rule may have more than one command, each on its own line. Note that you need to put a tab character at the beginning of every command line. So, make sure you use a tab for indent, not a space.

  • Here, output.txt dependes on code.x, which in turn depends on three .o files, which in turn depend on three .f90 source files. Dependencies are checked by date-stamp. For instance if the .f90 is newer than the .o file, make recreates the .o file.

Makefile2

In the second version we use a single rule for creating all .o files from all .f90 files:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Makefile2

output.txt: code.x
	./code.x > output.txt

code.x: main.o sub1.o sub2.o
	gfortran main.o sub1.o sub2.o -o code.x

%.o : %.f90
	gfortran -c $< 

Makefile3

The three source files are compiled in the same way. In the previous version we had to write out the list of .o files twice. This may increase the chance of introducing errors. We can avoid this using “macros”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Makefile3

FC = gfortran
OBJECTS = main.o sub1.o sub2.o

output.txt: code.x
	./code.x > output.txt

code.x: $(OBJECTS)
	$(FC) $(OBJECTS) -o code.x

%.o : %.f90
	$(FC) -c $< 

FC and OBJECTS are macros (or makefile variables). They are used by $(FC) and $(OBJECTS) in the rules.

Makefile4

In the fourth version we add a Fortran compile flag (O3 for level 3 optimization). We also add a phony target clean that removes the files created when compiling the code (.o and .exe files) in order to facilitate cleanup.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Makefile4

FC = gfortran
FFLAGS = -O3
OBJECTS = main.o sub1.o sub2.o
.PHONY: clean

output.txt: code.x
	./code.x > output.txt

code.x: $(OBJECTS)
	$(FC) $(OBJECTS) -o code.x

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

clean:
	rm -f $(OBJECTS) code.x output.txt

Notes:

  • A phony target is one that is not really the name of a file; rather it is just a name for a command to be executed. Here, the target clean is phony because it does not create a file named clean.

  • We execute this makefile using the following shell commands:

    $ make -f Makefile4
    $ make -f Makefile4 clean
    

Makefile5

We can add more tasks (as phony targets) to the makefile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Makefile5

FC = gfortran
FFLAGS = -O3
OBJECTS = main.o sub1.o sub2.o
.PHONY: graph clean

output.txt: code.x
	./code.x > output.txt

code.x: $(OBJECTS)
	$(FC) $(OBJECTS) -o code.x

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

graph: 
	nohup matlab -nosplash -nodisplay < plot_sin.m > o.txt
	open -a preview plot_sin.eps

clean:
	rm -f $(OBJECTS) code.x output.txt plot_sin.eps o.txt

We execute this using the follwoing shell commands:

$ make -f Makefile5
$ make -f Makefile5 graph clean

Further reading:

For further information see: https://www.gnu.org/software/make/manual/make.html