Makefiles
February 2020
Back to index.html.
Makefiles
The make
command is a traditional Unix tool to build an executable. It understands the dependency among files and figures out the order of files to be compiled by reading a configuration file (makefile). If your program consists of more than 2 files, make
helps you to compile the files.
A simple makefile
Dependency graph and makefile
Assume we have a module (mymod.f90
) and the main program (main.f90
) and each program unit is stored in a separate file. The module should be compiled first. The main program has to link the module object. The dependency is represented as the following figure.
This dependency graph shows that the main program requires the module. Although the main program requires depends on two files (mymod.mod
and mymod.o
), these two files are generated at the same time, so we would say that main.f90
depends on the presence of one of these 2 files. With a separate compilation, you have to run the compiler 3 times.
gfortran -c mymod.f90
gfortran -c main.f90
gfortran main.o myprod.o
A makefile represents the graph in a special syntax. Although the name of the makefile is arbitrary, we usually use Makefile
or makefile
because the make
command looks for such files by default. Here is a simple makefile to describe the above dependency.
a.out: myprod.o main.o
gfortran main.o mymod.o
main.o: main.f90
gfortran -c main.f90
mymod.o: mymod.f90
gfortran -c mymod.f90
Save this content as a file, Makefile
, and simply type make
in your terminal. The command reads the makefile and compiles the files in the requested order.
Structure of makefile
A makefile consists of repeated rules of a target (product) file, dependent files, and the command to generate the target.
target : dependency files (ingridients)
command to generate the target
The target file is a single file. The order of files in the dependency list is arbitrary. The target and the dependency files are separated by :
(colon). In the command line, you must insert a tab character at the top of the command. The tab is a marker to start the command in a makefile, so it can not be replaced with spaces. You can put 2 or more command lines; each command should be in a separate line (with a tab character).
You should repeat this block as many as you need. In the above example, you have to run a command (compiler) 3 times, so you need at least 3 blocks to describe the dependency graph.
By default, the first rule is for the final target. The order of remaining rule is arbitrary. In the above case, the final target is a.out
, and make
creates a dependency graph. You can place the final target at any place in a makefile, using some techniques explained later.
In the makefile, characters followed by #
will be ignored as a comment. It is similar to a shell script and some scripting languages.
How it works
The make
command creates the dependency graph. It means that this command knows which file is need to generate the final target. For example, in the above case, when main.f90
is updated but mymod.f90
is unchanged, you just need to re-compile main.f90
only, and link the new main.o
and the existing mymod.o
. The make
command will do it.
The command checks the timestamp of the target file and the dependency files. If the target is older than the ingredients (or the target does not exist), the program runs the specified commands. This smart compilation saves much time when there are many files in your project.
Summary
- The
make
command creates a dependency graph of files involved in a compilation process. - The graph is described in a makefile. The default name is
Makefile
ormakefile
. - A makefile has several rules. Each rule consists of the target name, its dependency files, and the commands to generate the target.
- The first rule describes the final target.
- The command line must starts with a tab character.
- The
make
command compares the timestamp between the target and each one of the ingredients. If the target is old (or not found),make
invokes the commands. - The character
#
leads a comment line.
Exercises
- Change the order of rules in the makefile shown above. Make sure the changed file does not work.
- Remove a tab character from the makefile shown above, and make sure it does not work.
Useful features for makefile
Macros
A makefile can have a macro, which is used as a variable. The above make file can be generalized using some macros.
FC = gfortran
a.out: myprod.o main.o
$(FC) main.o mymod.o
main.o: main.f90
$(FC) -c main.f90
mymod.o: mymod.f90
$(FC) -c mymod.f90
The macro is defined as macro_name = value
, usually placed at the top of the makefile. The macro name separates upper and lower cases. To expand the macro (i.e., to get the value), use $(macro_name)
. Note that $
is required when referring to the macro. In the above case, a macro FC
(for Fortran compiler) defines a compiler so that you can just modify the macro to change the compiler.
There are many features in macros. Please search the possible usage of macros by yourself.
Phony target
The makefile has several pre-defined targets, and .PHONY:
is one of them. It defines a dummy target that is not a file name. Typical usage of .PHONY
is to define a more meaningful target-name than a file name.
Assume your makefile has the final target a.out
, but it is not at the top. You may come up with the following makefile to specify the final target.
FC = gfortran
all: a.out
main.o: main.f90
$(FC) -c main.f90
mymod.o: mymod.f90
$(FC) -c mymod.f90
a.out: myprod.o main.o
$(FC) main.o mymod.o
The first target is all
, which is not a file, but it just indicates the final product. Although it works, it becomes clumsy if you have many such dummies and real files in a makefile. The .PHONY
target clarify which targets are dummy. The dummy target all
is often used to refer to the final target.
FC = gfortran
.PHONY: all
all: a.out
main.o: main.f90
$(FC) -c main.f90
mymod.o: mymod.f90
$(FC) -c mymod.f90
a.out: myprod.o main.o
$(FC) main.o mymod.o
Another typical usage of dummy targets is to remove all existing files. A dummy target, clean
, is used in practice for this purpose.
FC = gfortran
.PHONY: all clean
all: a.out
main.o: main.f90
$(FC) -c main.f90
mymod.o: mymod.f90
$(FC) -c mymod.f90
a.out: myprod.o main.o
$(FC) main.o mymod.o
clean:
rm -f a.out *.o *.mod
Options of the make command
Selective compilation
If you obtain a target described in the 2nd or later rules rather than the final target, you can specify the target as an option of make
in the terminal. For example, in the above case, the following command tries to generate main.o
instead of a.out
.
make main.o
Arbitrary name of the makefile
If the name of a makefile is not Makefile
and makefile
, you can specify the name in the terminal. For example, when the makefile is makefile.txt
, type the following command in terminal.
make -f makefile.txt
Parallel compilation
Some files may be independent, and these files can be compiled at the same time. The make
command finds independent files and runs the commands in parallel with an option.
make -j 4
It can use at most 4 threads if possible. The option -j
, meaning jobs, specifies the number of threads to generate the target. This option is not useful in a small project, but it becomes very efficient with many files. Note that, if the dependency graph is broken or incomplete, the compilation may fail with this option.
Back to index.html.