A tutorial for using
To be read after the tutorial about the compiler toolchain.
makeutility formalizes as a directed graph the problem of compiling several source files. The vertices of the graph are files and the edges are the instructions to transform the corresponding files. Vertices with only outgoing edges are the source files. Vertices with only incoming edges are the target files. Vertices with both incoming and outgoing edges are intermediary files. The build process is performed by creating the target files according to the instructions given on this directed graph. The edges of this directed graph are called rules. The
makeprogram comes with a large set of predefined rules, and these rules can be augmented or changed by writing a
The goal of this tutorial is to showcase the timeless beauty of Makefiles, not to give a set of recipes.
make program is an extremely elegant UNIX utility. It solves
the problem of building a complex program given a formally described set of
dependencies. With the
-j option, it builds as many files as
possible in parallel.
In this tutorial we explain several use cases. We start with the simplest possible case (compiling a single file), and we move to more complicated cases (like compiling several files, or linking against libraries).
It is not necessary to write any makefile for using
example, when we want to build a single file. We start with this simple
example, that already illustrates almost all the features of
Imagine that you want to compile a simple program
Then you run
And it will compile your program (by running
cc hello.c -o hello).
This is the whole story. Using
make is always the same: you ask
make to build one file, and he tries to do it, in the most meaningful way.
Everything else are minor details, like setting the compiler options; or minor
variations, like building more than one file.
In the previous example, note that running
make hello a second
time does nothing. Since the file
hello is already built, there's
nothing else to do. The program
make is idempotent: running
make two times is the same as running it once. This is a
fundamental property. How does the program know that there is no more work to
do? Because it looks at the dates of each file.
Thus, to force a recompilation, you can change the date of the source code
(e.g., by running
touch hello.c) and then
build it again.
Now try the following:
Notice that, when the file
hello.o already exists,
make nows that the source is already compiled, and it only needs
to link it to produce a final executable. It always tries to build the
requested file with the least possible amount of work. If you
hello.o and then you run
make hello, it will only do the
linking, because the file
hello is older than
make know what to build from what?
Because he has a secret list of implicit rules that tell it so.
These rules form a directed graph, which in our case is the following:
hello.c ---------------------------> hello.o -----------------------> hello cc -c hello.c -o hello.o cc hello.o -o hello.c
The nodes of this graph are the filenames. The edges of this graph are the
instructions to create one file from another. When you run
hello, the program checks if the requested file appears in the graph,
and finds a directed path from a file that exists to the requested file.
Then, the program runs the instructions corresponding to each edge in the
path. It only runs the part of the path from the file that is newer than the
requested target, if any.
This is all that
The only control left to the user is the specification of the graph.
You can set variables to fine-tune the build process. The most important
CC, that specifies the
C compiler, and
the default value is typically
"cc". There are two
ways to change the value of a variable (without writing a Makefile): either you set an environment variable of the same name, or you specify it as argument to the
The following table summarizes the most important variables
|CFLAGS||flags for C compiler||-O2 -Wall|
|CXXFLAGS||flags for C++ compiler||-O2 -Wall|
|CPPFLAGS||flags for preprocessor||-I /path/to/my/includes
|LDFLAGS||flags for linker||-L /path/to/my/libs|
|LDLIBS||libraries for linker||-lmylib|
Variables are very powerful. For example, the following shell script builds the same program using five C compilers and different compiler options (debug and release mode):
This script creates 10 different executables with all the compilers and all the
compiler options (assuming than the named compilers are installed). These kind
of scripts are useful to check that your program gives zero warnings for a
large set of compilers and compiler options. And we have not written a
Making without makefiles may be an interesting exercice, but it is not more
practical than calling the compiler directly. The real interest of
make is that it allows to compile many files into one
program—or many programs—in a single stroke.
This is how you would specify the graph of the example above in the
That's a complete
It is a list of rules. Each rule has the following form:
Very important: the instructions are indented using one tab. Spaces will not work.
Once you have written a makefile, you can request to build a target by running
make target. If you don't specify a target, the first target will
This simple makefile, where the rules are explicit, is actually less powerful
than an empty makefile that uses the implicit rules. There are two ways in
which it is less powerful: (1) we cannot change the compiler or the flags; and
(2) the name
hello is hardcoded, it does not specify how to build
files with different names. The first problem is solved using variables. The
second problem will be solved later using pattern rules.
You can define variables inside the makefile and use them later in the rules:
Variables defined inside the makefile are taken as default values. They can be
overridden by redefining them as arguments in the call to
course, the variable names that we have chosen in this example are
preposterous. Here, we should have used the standard names, which are
already given default values, and other people can expect default behaviour
from our makefile (such as taking into account their preferred compiler flags):
Notice that the makefile above is equivalent to an empty file, because it matches an implicit rule. However, for clarity we will work on this simple example and add more files to build. Until section 3.6, forget about implicit rules.
For now, we have just dealt with a single file to compile. In a typical
case, the source code of a program will span several files (let's say, three
we can specify the rules for building each file:
make will call the compiler four times: one for each
object file, and then one to link all the objects into one exectuable. This is
embarrassingly parallelizable; indeed running
make -j will launch
the compilation of the three object files in parallel.
In the example above, there is a lot of redundancy: the names of the files appear many times. The redundancy can be removed by using local or automatic variables:
|%@||name of the target|
|%^||list of all prerequisites|
|%<||the first prerequisite|
|%*||the stem of a pattern rule|
Thus, when the name of the target or of the prerequisites appears inside a rule (which happens almost always), we can simplify the rule using local variables:
The makefile that we have just written above exhibits a higher-level type of redundancy: the rules themselves are all the same! Moreover, the names of each target and its prerequisite are the same, only differing by extension. Pattern rules allow to express this kind of redundancy:
% is a placeholder for an arbitrary string. If you
request to build a file with the extension
.o, then this rule will
match, and it will try to build the
.o file from the
.c file in the indicated way.
In the makefile above, notice that the rules for building the object files are unnecessary, because they do exactly the same thing as the implicit rules. Thus, an equivalent makefile is the following.
Rather short, isn't it? You just say that you need
the implicit rules take care of building it from
Now we are in the rarefied atmosphere of theories of excessive beauty and we are nearing a high plateau on which geometry, optics, mechanics, and wave mechanics meet on a common ground. Only concentrated thinking, and a considerable amount of re-creation, will reveal the full beauty of our subject in which the last word has not been spoken yet. —Cornelius Lanczos, The Variational Principles of Mechanics.
The two-line makefile above can be further shortened to this thing of beauty:
This is a complete makefile, equivalent to the examples given above. How is that even possible? What kind of sorcery is going on here?
This works because the multiple prerequisites of the same target can be stated on separate lines, and they are simply added to the rule (there must be exactly one rule per target). Thus, without using implicit rules and writing the prerequisites separately, this is equivalent to the following
When we put all the prerequisites on the same line and expand all the patterns, we recover EXACTLY the same text as in section 3.3.
We have talked before about a "secret" list of implicit rules. Actually, there is nothing secret about it. The implicit rules are defined explicitly by pattern rules and they look exactly like the last two patterns of the previous section. To look at the complete list of implicit rules run the following command:
This will create a text file with the list of all implicit rules (and many
other information). Running
make without a makefile is exactly
equivalent to using this file. Now, this file may seem overwhelming; it is
probably very long, because
make uses a lot of heuristics, and
they are all specified here. But somewhere in the middle it must contain
lines that look more or less like this:
...which is just the two pattern rules on section 3.6. See section 6.4 below for a more complete view of the default pattern rules.
It is highly recommended to print the list of implicit rules for your make setup, and read it thoroughly. Even if it is long, it is nothing more than a sequence of variable assignments and pattern rules.
It is not necessary that a rule creates any file,
make will not
verify anyway; you can run all sort of crazy stuff in the instructions. The
most typical is to have a
clean target, that instead of creating a
file called "clean" simply removes all the executable files. Or you
can have a
check target that runs unit tests in your code.
This is then our fancy makefile:
Notice that the
clean target has no dependences. The
check target has the file
hello as dependency, so it
will compile the
hello file if needed.
NOTE: if you have files named
then all hell will break loose. To protect against this risk, you can
precede these targets with a line that says
.PHONY:". But I like to live on the edge.
Given the makefile above, the following shell script builds the program using five C compilers, and runs the test suite for each of the resulting executables, both in debug and in release mode (for a total of 10 checks of the test suite):
If you want to be really neat, design the test suite so that it is silent upon
success, and add the
-s (silent) option to
Then, the script will only produce output when something fails.
Try doing that with
In an ideal world (from the point of view of the makefile writer), your program is written from scratch using an old standard of the programming language, and it uses no external libraries. In practice, however, your program may rely in some modern features of the language—that require compiler flags—and it may need external libraries which are installed under strange names. Also, the dependences between the source files may be somewhat convoluted, and writing them by hand is error-prone. Let's see what we can do about all these problems.
make will never try to find where external
libraries are located; it is just not his job. In theory, this is not a
problem at all. If your program requires e.g., the
library, then you simply add
-ltiff to the compilation line.
The following makefile compiles a program that requires libtiff:
This will work correctly as long as libtiff is installed on your system. What does it mean, exactly "to be installed in your system"? Well, by definition, it means that this makefile works! More precisely, it means that the following three things are true:
#include <tiffio.h>in your source code, the preprocessor finds this include file.
-ltiffto the compilation line, the linker is able to find the library file.
libtiff.soin your system.
For example, if the program
hello of section 3.7 requires
libtiff, then this is a complete makefile for compiling the
This works because
$(LDLIBS) is used in the implicit rule for
For GNU and BSD systems, libraries are often correctly installed: once you install a library using the package manager, this library becomes available to the compiler without further ado. In case the library is not installed, the compilation will produce a clear error message, which I suppose is the desired behaviour.
In other situations (e.g., bizarre systems without package managers like OSX, or user-installed libraries), you may want to use a library that is not "correctly installed" according to the three points above. Then, the solution is to correctly install it! This can be done by setting three environment variables:
.a) to the variable
Once these three variables are set, then the library is correctly installed, and your makefile can be run. Notice that this task is independent to the usage of the makefile; the task is part of the installation of the library, for systems that do not have a decent package manager. These variables are recognized by all compilers that I know of (GCC, CLANG, TCC, INTEL and SUN compilers).
It is strongly advised to write portable code that compiles out of the box in any system. Today, this is much, much, easier than a few years ago because most systems are POSIX-compliant (with slightly different versions of the POSIX standard, though). Thus, horrible ``portability'' tools like automake, autoconf, and cmake are mostly unnecessary.
Still, there are a few situations when your code with a simple makefile is
not straightforwardly portable to all the systems that you may want to.
I show to examples: to compile ANSI C in older versions of GCC, and to
``enable'' openmp in the platforms where it is available. Once you
understand these hacks, you can easily rewrite them for other simtuations.
The basic idea is to run a shell command that will return or not an empty
output, according to the condition you want to check, and then capture this
output from within a
$(shell ...) directive:
If you have a favorite Makefile hack, you can send it to me and I add it here.
Often, automatic generation of dependences is unnecessary: each
object file depends on the corresponding source file (that has the same name
but different extension). This case is already covered by the implicit rule
However, there are still some situations when there are other dependences
between source files. For example, when you
file from your source code. In principle, since the file is explicitly
included, it should not appear in the command line and the compilation will
be successful without explicitly stating this dependency. Thus, the short
answer is that even in that case, this dependence need not be known by make.
But when you are developing code, the situation is different: if you change the included file, you may want to recompile the object. Thus, make must know about this dependency.
Fortunately, most compilers accept the
-MM option, that
prints the list of files included by a source file, conveniently formatted as
makefile dependencies. The following makefile deals automatically with this
This is the case that we have solved above
This can also be done using only implicit rules.
When you have a set of object files common to a set of separate executables.
This is just like the previous case but putting all the objects inside a static library for ease of linking.
You can ``enhance'' the example above with compiler options and additional libraries to obtain a very general Makefile:
Notice that there is no harm at all in linking unused object files. The
linker will actually ignore symbols that are unused. Similarly with
libraries given with as
pkg-configand the like
pkg-config tool provides a way for people to use libraries
that are not fully installed on their system. This is a simple program
that prints whatever horrible flags are necessary for compiling and linking
against the library. It has three relevant options, here shown with their
output on my system (for lib-poppler):
The output of pkg-config is straightforward to use inside Makefiles
Other packages, suck as gdal, prefer to avoid the standard
pkg-config system and provide their own
with similar behaviour. Thus, if your program requires support for gdal, you
simply do the following:
All the examples given above work on a flat directory structure. This simplification allows to harness the full power of implicit rules. If you want to work with more complex directory structures, you will have to write the patterns yourself. Here we show an example of separate source and output directory (for the many objects/many executables case)
I would advise to only split your source code into subdirectories when you have a lot of files (say, more than 100).
In what language is a Makefile written? The answer is: in three separate and different languages:
Moreover, there is a set of pre-defined macros. This is actually very important since it allows to write extremely succint makefiles.
The core makefile language describes a directed graph explicitly. Each edge is written either using a tab:
or a semicolon
The vertices are filenames, and the edges are shell instructions.
This core language is extremely portable along all the historical implementations of make.
The edges of the graph, or rules, are written in plain UNIX shell. This text is first pre-processed by the makefile macro language, replacing the dollar-variables that it finds before sending the text to the shell. Thus, if you want that the shell receives dollar characters, you have to escape them (with another dollar character).
Formally, it is easy to distinguish between the make language and the shell
language parts of a Makefile: lines starting with TAB are interpreted by the
shell, and the other lines are interpreted by make. This is almost true, the
shell is also used inside makefiles in another place: as the first and only
argument of the
$(shell ...) directive.
If you really need to, you can change the actual shell used for running the
programs by changing the make variable SHELL. For example
SHELL=/bin/zsh. But it is strongly advised to use only posix
The makefile macro language is the ``fancy'' part of the Makefile. It is largely non-portable, but equivalent constructions exists between the two main implementations of Make: BSD make and GNU make. While it is possible to write makefiles in a portable way, they do not tend to be beautiful (mainly because the implicit rules are slightly different).
You have to think of the macro language as a pre-processor of your makefile, just like the C preprocessor. It expands the macros in your makefile until creating a makefile with only the core language constructions. The following features are available
There is a ton more of available features in the GNU macro language. The GNU Make manual has more than 200 pages!
The output of
make -p is indeed overwhelming.
Yet, the only lines of concern for C and C++ are the following:
When you run make without a Makefile, it is just as if this file was already present.
Forget about compiling. The
make program is useful in a very
general situation: whenever you want to run a complex pipeline with many
intermediary files. Typically this task is correctly accomplished by writing a
shell script or (god forbid) a python script. However, we show that
using a makefile may be a better idea.
For example, consider the following simple script that registers several images and computes the fusion of them all:
This is a classical computational photography problem, and this script is a perfectly acceptable way of solving it. Shell scripts are cool. Yet, we will see how to improve it a bit.
This script runs the tasks in series. This is wasteful on a large computer
with, say, 32 cores, because it could be running the tasks in parallel. No
problem, GNU parallel is very easy to use. You simply print the instructions
that you want to run, and pass the resulting text to
This version of the script will run all the tasks in parallel and will be much faster.
Is this the best parallelization possible? No. Notice that if, for example, one of the tasks on step 2.1. takes much longer than the others, there will be a long wait between steps 2.1. and 2.2., during which only one processor will be working. How can we solve this problem? In this case it seems easy, we just have to parallelize at a coarser level, sending to GNU parallel lines that contain the whole computation for each file. But if the dependences between files are more complicated, the problem becomes difficult very soon.
Another issue with the first script is that it always runs all the steps. Imagine that you change the fusion criterion in the last line of the script. Then, when you re-run the script all the steps are performed, but this is wasteful because all intermediary files are identical. The typical solution to this problem is to COMMENT all the script except the lines that you want to re-run. But of course this is ugly. A slightly better option is to check whether the updated files will be changed or not before recomputing them:
Notice that this is just the original script, but you add an explicit test before executing each line. If the input file is older than the output file, then you do not run the line. This is a simple modification that turns your script into a much more useful one. It has the following nice properties:
Yet, it has the following problems
reg_*files, not only the first one. If you delete file
reg_3.pngand rerun, it will recompute this file, but it will not re-build the output
There is, after all, a free lunch. If you rewrite your original script as a makefile:
Now you get, for free:
I say ``for free'' because this makefile has essentially the same length and complexity as the original script, and it is just as easy to write (once you are fluent in makefile language).
Notice that you can join several rules into the same target...
...to obtain a very short makefile. Some people is really into this sort of thing. Personally, I prefer to give an explicit target for each intermediate file, but this is just a matter of taste. This is the kind of discussion to have among other native makefile speakers, sipping scotch next to the fireplace.
makein your system:
make -p -f /dev/null, that prints the set of implicit rules.