Makefile tutorial
For some reason it seem that Makefiles has gotten the reputation that they are hard to use and learn, almost to the point that people just seem scared even to deal the slightest with them. Like it’s some kind of crazy magic of the old ones. At least in the linux world there are a lot of other build systems, where actually many of more popular acutally generate Makefiles. In my experience this is a bit harder to troubleshoot, as you both need to troubleshoot the system that generated the Makefile, and the generated Makefile itself. So having some knowledge on how Makefiles work is really handy.
Many of the tutorials I’ve read go to great lengths on drawning dependency graphs etc to explain Makefiles. For me that just didn’t do it, so I hope that this is description that would have helped me getting started.
This text will look at C projects, but most of this can be translated to
pretty much any input that need some kind of processing to create some kind of
output. I will start with explicit Makefiles, and work my way through what
make
knwo and can help you with
Makefiles track changes in input files and use that information to build new output files when the input is newer than the input. So for a C program, the listing would be something like this:
program: program.o library.o
program.o: program.c
library.o: library.c
program
is built by linking program.o
and library.o
. And these two object
files are built from their respective C source code. Already at this level
make
can draw two important conclusions: If I only change library.c
then
there is no need to recompile program.c
, and there is no dependency between
library.o
and program.o
which means they can be built in parallell.
make
need know how to convert input files to ouput files, even though make
already know how to do this for a lot of file types as you’ll see later. For the
example above it would be something like
program: program.o library.o
gcc -o program program.o library.o
program.o: program.c
gcc -c program.c
library.o: library.c
gcc -c library.c
As you can see the commands needed to convert the input source files to target
built files are listed indented below the dependency listing. This need to be a
tab, make
is really picky about this.
Even though this works, you can see that this is very error prone and sensitive to typos, which leads us to…
Variables
Variables can be used to make the Makefile easier to maintain, so say if we want to be able to easily change the compiler, then we would need to write something like this
CC = gcc
program: program.o library.o
$(CC) -o program program.o library.o
program.o: program.c
$(CC) -c program.c
library.o: library.c
$(CC) -c library.c
Variables are expanded with the syntax $(VARIABLE)
. The CC
variable used
here is commonly used to indicate the C compiler. Other commonly used variables
are CFLAGS
for flags to the compiler, CXX
for the C++ compiler etc.
There are also a bunch of variables that are called automatic variables.
Automatic variables are variables that expand to meaningful information in the
the instruction part of the makefile, which is also known as recipe. The
automatic variables are a bit cryptic in their naming, as they are called things
like $@
and $^
, which are also about the two automatic variables that you
really need to care about. The manual has a full list of automatic variables
for reference.
The $@
variable expands to the target file, and $^
expands to all the input
files. So the example above can now be shortened to
CC = gcc
program: program.o library.o
$(CC) -o $@ $^
program.o: program.c
$(CC) -c $^
library.o: library.c
$(CC) -c $^
Making the Makefile shorter
There are a couple of steps that can be used to make
the above build script
shorter: First it can be noted that both compile recipes are the same, and they
will be for all C files. So by using the wildcard, %
, this can be written as
program: program.o library.o
$(CC) -o $@ $^
%.o: %.c
$(CC) -c $^
As it happens, make
actually alread knows how to create object files from C
source code. make
has a list of built in implicit rules. For example, make
knows that C files should be compiled with
$(CC) $(CPPFLAGS) $(CFLAGS) -c %^
make
also know that rograms should be linked with
$(CC) $(LDFLAGS) %^ $(LDLIBS) -o %@
This might be a bit much to take in att once, but what this means is that as
long as you use the variables listed above, then you actually don’t need to
write any recipes for C programs. As long as you use CFLAGS
for C flags,
CPPFLAGS
for C Preprocessor flags, LDFLAGS
for linked flags, and LDLIBS
for libraries. So the example makefile can actually be shortend to
CC = gcc
program: program.o library.o
One quirk to take note of here is that the name of the output file must be the
same as one of the input files, in this case program
. Just as with automatic
variables the is a catalogue of implicit rules in the manual for reference.
A tiny special case
If you only need to build program
from program.c
you actually don’t need any
Makefile at all, you can just type make program
, which will expand to a
compilation and a linking of program.c
to program
Beyond the basics
The steps listed above are pretty much all you need for a really basic build script. You can probably leave this text now, and start using Make.
However, for more complete build scripts you likely need code for cleaning up built files. But, as you might realize, removing a file does not result in an output file. To be able to support this Make uses something called phony targets. Phony targets are targets that does not result in actual files, but you want to run for some kind of side effect.
Common phony targets include all
, clean
and install
. all
is
often the first target in the makefile, which is also the target make
defaults
to. This is often used to build everything.
To clean up built files the target clean
is often used, and for installing the
target install
is often used.
Adding these targets to the exmple script would look something like this
.PHONY: all clean
CC = gcc
all: program
clean:
rm -rf *.o
program: program.o library.o
Common structuring and final notes
There are probably as many structures of Makefiles as there are developers, and many Makefiles can be really hard to decipher. For my part I’ve found that each target directory that should contain the built files is usually the best place to place your Makefiles. For the most simple case this may be the same directory as the source code. For the more complex scenarios this can be a separate directory.
For more complex build systems, where different targets have different ouput
directories it might be worth spending some time to write some common code that
can be included using the include
directive.
If you are using gcc
, then it might be worth knowing that gcc
can create
files for dependency tracking, which allow make
to rebuild files if there
included header files have changed. This is something that is hard to do in
normal makefiles. To generate this kind of ouput you need to give gcc
the
-MMD
flag, and then use
-include *.d
This tells make
that include all .d
files, but if the files does not exist,
then it is not an error.
Hope this text was useful for you, and good luck with your Makefiles.
Common conventions
- To support cross compilation of your code it is common to use the variable
CROSS_COMPILE
like thisCC = $(CROSS_COMPILE)gcc
DESTDIR
andPREFIX
are used to control where you install files, and they can be used likeINSTALLDIR $(DESTDIR)/$(PREFIX)/bin
, wherePREFIX
can be set usingPREFIX ?= usr
- Use
all
as the first target, which does a full build.