Makefiles¶
Makefiles and the associated program make
are the defacto standard for building
software on Unix style machines. Makefiles can also be a little bit arcane in their
structure owing to just how long they have been around. The fundamental purpose of
makefiles is to define all of the compilation instructions for the source code in a
project, and to recognize the dependencies therein.
There are more flexible and/or modern tools like autotools, cmake, meson, or others. Most of these ultimately generate Makefiles, but provide an extra layer on top of it that can be helpful for complicated projects with many (external) dependencies that also need to run on many different systems. These tools are pretty clunky on smaller code bases, so we’ll ignore them in this class.
Makefile Rules¶
A makefile takes a set of rules in the following format
target ... : prerequisites ...
(tab character) recipe
(tab character) ...
A target usually refers to a file name that is generated by compiling or linking your code.
A prerequisite is a file that is required as input in order to generate the target. These prerequisites may also be targets that have their own dependencies.
A recipe is an action that takes place when
make
builds a target. You must offset the recipe with atab
character. This will not work with spaces.You can technically change the character used for offsetting the recipes, but we’re going to ignore that for now.
Makefile examples¶
Here we provide an example Fortran code fullcode.f90
that consists of a main program
and two subroutines:
1! /codes/multifile/fullcode.f90
2
3program demo
4 print *, "In main program"
5 call sub1()
6 call sub2()
7end program demo
8
9subroutine sub1()
10 print *, "In sub1"
11end subroutine sub1
12
13subroutine sub2()
14 print *, "In sub2"
15end subroutine sub2
To illustrate the construction of a Makefile, we’ll first break this up into three separate files:
1! /codes/multifile/demo.f90
2
3program demo
4 print *, "In main program"
5 call sub1()
6 call sub2()
7end program demo
1! /codes/multifile/sub1.f90
2
3subroutine sub1()
4 print *, "In sub1"
5end subroutine sub1
1! /codes/multifile/sub2.f90
2
3subroutine sub2()
4 print *, "In sub2"
5end subroutine sub2
Below, we study several Makefiles that get successively more sophisticated to compile the code provided in this section.
In the first version we write out explicitly what to do for each file. In order to run this
Makefile
, just type make
. This will work as long as your makefile has one of a few a default
names like Makefile
or makefile
:
1# /codes/multifile/Makefile
2
3output.txt: main.ex
4 ./main.ex > output.txt
5
6main.ex: main.o sub1.o sub2.o
7 gfortran main.o sub1.o sub2.o -o main.ex
8
9main.o: main.f90
10 gfortran -c main.f90
11sub1.o: sub1.f90
12 gfortran -c sub1.f90
13sub2.o: sub2.f90
14 gfortran -c sub2.f90
This is pretty tedious, and not much better than just writing all of the compiler calls on the command line.
In the second version there is a general rule for creating .o
files from .f90
files,
called inference rules
or pattern rules (see more
pattern rules).
There are also an old style rules called suffix rules which we will not use in our course
(see article-suffix).
The last line (i.e., recipe) has the special macro $<
which corresponds to the name of the
first prerequisite (i.e., *.f90
files). There are seven frequently used
automatic variables.
In order to run this non-default Makefile2
, you need
to type in make -f Makefile2
:
1# /codes/multifile/Makefile2
2
3output.txt: main.ex
4 ./main.ex > output.txt
5
6main.ex: main.o sub1.o sub2.o
7 gfortran -o main.ex main.o sub1.o sub2.o
8
9%.o : %.f90
10 gfortran -c $<
In the third version we define the variable OBJECTS
so that we only have to write
out this list once, which minimizes the chance of introducing errors:
1# /codes/multifile/Makefile3
2
3OBJECTS = main.o sub1.o sub2.o
4
5output.txt: main.ex
6 ./main.ex > output.txt
7
8main.ex: $(OBJECTS)
9 gfortran -o main.ex $(OBJECTS)
10
11%.o : %.f90
12 gfortran -c $<
In the fourth version, we place the Fortran compiler into the variable FC
.
This allows a user to override the choice of compiler if they
want to. Additionally, compiler flags for debugging and optimization
(see this StackOverflow answer for more information about the BUILD
variable),
as well as a linker flag (blank in this example), have been added:
1# /codes/multifile/Makefile4
2
3FC = gfortran
4BUILD ?= debug
5fflags.debug = -g
6fflags.release = -O3
7FFLAGS = ${fflags.${BUILD}}
8LFLAGS =
9
10OBJECTS = main.o sub1.o sub2.o
11
12output.txt: main.ex
13 ./main.ex > output.txt
14
15main.ex: $(OBJECTS)
16 $(FC) $(LFLAGS) -o main.ex $(OBJECTS)
17
18%.o : %.f90
19 $(FC) -c $(FFLAGS) $<
Next we add a phony
target clean
that removes the files created when compiling
the code in order to facilitate cleanup. It is phony because it does not
create a file named clean
, rather clean
is an action.
To run clean
, type make -f Makefile5 clean
:
1# /codes/multifile/Makefile5
2
3FC = gfortran
4BUILD ?= debug
5fflags.debug = -g
6fflags.release = -O3
7FFLAGS = ${fflags.${BUILD}}
8LFLAGS =
9
10OBJECTS = main.o sub1.o sub2.o
11.PHONY: clean
12
13output.txt: main.ex
14 ./main.ex > output.txt
15
16main.ex: $(OBJECTS)
17 $(FC) $(LFLAGS) -o main.ex $(OBJECTS)
18
19%.o : %.f90
20 $(FC) -c $(DFLAGS) $<
21
22clean:
23 rm -f main.ex $(OBJECTS)
Fancier things are also possible, for example automatically detecting all
the .f90
files in the directory to construct the list of SOURCES
and OBJECTS
:
1# /codes/multifile/Makefile7
2
3FC = gfortran
4BUILD ?= debug
5fflags.debug = -g
6fflags.release = -O3
7FFLAGS = ${fflags.${BUILD}}
8LFLAGS =
9
10SOURCES = $(wildcard *.f90)
11OBJECTS = $(subst .f90,.o,$(SOURCES))
12
13.PHONY: clean
14
15output.txt: main.ex
16 @echo BUILD=${BUILD}
17 ./main.ex > output.txt
18
19main.ex: $(OBJECTS)
20 $(FC) $(LFLAGS) -o main.ex $(OBJECTS)
21
22%.o : %.f90
23 $(FC) -c $(FFLAGS) $<
24
25clean:
26 rm -f main.ex $(OBJECTS)
Further reading¶
remake, a make debugger
https://stackoverflow.com/questions/5950395/makefile-to-compile-multiple-c-programs, a makefile to produce multiple executables