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:
|
|
|
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