Makefiles

Yutaka Masuda

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.

Dependency graph with two example files

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

Exercises

  1. Change the order of rules in the makefile shown above. Make sure the changed file does not work.
  2. 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.