Practical Makefiles, by Example
Practical Makefiles, by Example
Contents
1 Rationale 1
3 Enter make 3
4 Simpler makefile 4
6 Further improvements 5
6.1 Multiple source directories . . . . . . . . . . . . . . . . . . . . . . . 5
6.2 Handling cross-platform differences . . . . . . . . . . . . . . . . . . 5
6.3 Wildcard rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
6.4 Automatic #include dependency tracking . . . . . . . . . . . . . . . 6
6.5 Building sub-projects . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1 Rationale
The purpose of this document is to explain how to write practical makefiles for your
everyday hacks and projects. I want to illustrate, how easy it is to use make for building
your programs, and doing so, dispel the notion that resorting to big clunky graphical
IDEs, or makefile generators such as autotools or cmake, is the way to focus on your
code faster.
The purpose of this document is certainly not, to teach you how to write user-
friendly, all-encompassing, release-quality build systems. You can certainly arrive to
that by extending a simple makefile, and I’ll give you a few pointers about that sort of
thing in the end, but I will not focus on complexity and completeness, but rather on
simplicity and practicality.
1
Finally, it’s worth spelling out up front that when I say "make", I’m really refer-
ring to the GNU implementation of make, which is packed with useful features, not
necessarily found in other make programs out there. It might be interesting to write
a follow-up article, providing similar examples for other make implementations out
there, such as BSD make, Watcom make, Borland make, or Microsoft’s nmake. But
I’ll leave that for another time.
To build this program we would first have to run the compiler on each source file,
generating a corresponding object code file:
cc -c main.c
cc -c game.c
cc -c level.c
...
Then, we would have to feed all the object files to the linker (possibly still using the
C compiler front-end for convenience), instructing it to link all the libraries we need as
well:
2
Obviously, such tediousness could be automated with a shell script. However, that
would be a grossly sub-optimal solution, as it would recompile all source files from
scratch, every time we try to build with it.
3 Enter make
Make is a much more elegant solution to our building problems. We create a file named
Makefile, which contains a set of rules describing build products, their dependen-
cies, and which commands are needed to build them. Then when we ask make to build
our program binary, it recursively traverses the dependency graph to figure out which
parts need to be re-created, based on the last-modified timestamps of the target file, and
its dependency files.
Warning: do not run away terrified by the first makefile example. It’s only meant
to illustrate how make works, so it’s needlessly verbose. If you’re easily scared, skip
to section: "A makefile for 99% of your programs".
Back to our game example, the binary mygame, obviously depends on main.o
(among other object files). main.o in turn, is created by compiling main.c. So,
let’s define two make rules for creating these two files:
main.o: main.c
cc -c main.c
player.o: player.c
cc -c player.c
3
4 Simpler makefile
Obviously, having to type rules for each of our source files is tedious, and thankfully
unnecessary. Make actually knows how to create object code from C source files, so we
can skip the object file rules, and also provides some handy variables for referring to
the target or dependency files in rule commands without having to re-type everything.
So here is a slightly simpler makefile for the same task:
myprog: $(obj)
$(CC) -o $@ $^ $(LDFLAGS)
.PHONY: clean
clean:
rm -f $(obj) myprog
The first line of this example collects all source files in the current directory in
the src variable. Then, the second line transforms the contents of the src variable,
changing all file suffixes from .c to .o, thus constructing the object file list we need.
I also used a new variable called LDFLAGS for the list of libraries required during
linking (LDFLAGS is conventionally used for this usage, while similarly CFLAGS and
CXXFLAGS can be used to pass flags to the C and C++ compilers respectively).
4
Finally, I added a new rule for cleaning up every target, in order to rebuild the whole
program from scratch. The clean rule is marked as phony, because it’s target is not
an actual file that will be generated, but just an arbitrary name that we wish to use for
executing this rule. In order to run any rule other than the first one, the user needs to
pass the target name as a command-line argument to make. So, for instance, to clean
everything in this example, just type make clean.
6 Further improvements
The makefile listed above is sufficient for most small programs you’ll ever write. In
this section, I’ll go over a few improvements for larger projects.
mygame: $(obj)
$(CXX) -o $@ $^ $(LDFLAGS)
The rest is exactly the same as previously. Note that I used the CXX built-in variable,
which defaults to c++ instead of cc, to invoke the linker in the correct way for C++
programs, automatically linking libstdc++ with it. Also, backslashes at the end of
lines serve to merge multiple lines together by escaping the newline character.
5
libgl = -framework OpenGL -framework GLUT
else
libgl = -lGL -lglut
endif
The order of defining libgl and defining LDFLAGS doesn’t matter, as long as
they’re both before the first rule which uses either of them, since LDFLAGS won’t be
evaluated (and thus require substitution of the libgl variable) until it’s used in a rule.
A slightly more elegant, but not exactly equivalent way of doing the above would
be the following instead:
I said not exactly equivalent, because in this way we would have to enumerate all
possible UNIX systems we’d like to support instead of just handling them in the else
part of the conditional statement.
main.o: main.c
$(CC) $(CFLAGS) -o $@ -c $<
But let’s say you want to compile code for a new language, which make knows
nothing about. There is a mechanism to write generic rules, so that you won’t have to
laboriously type specific rules for each file you wish to compile.
If you wish to inform make, on the generic form of rules for building object files out
of source files in a fictitious foo language, which are commonly in files with a .foo
suffix, and compiled by running fooc -c, you can write the following wildcard rule:
%.o: %.foo
fooc $(FOOFLAGS) -o $@ -c $<
Then, whenever make needs to build xyzzy.o, and there is a file named xyzzy.foo,
it will use that rule to compile it.
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $<
Which means, that if we change foo.c, foo.o will be out of date and will be
rebuilt automatically. There are, however, usually many more dependencies that need
to be considered to determine if foo.o needs rebuilding, which fall through the cracks
in this simple but generic scheme.
6
Specifically, if foo.c also includes foo.h and bar.h, then if we fail to rebuild
foo.o when either of these header files change, will result in a potentially incorrect
program, and depending on the nature of the modifications, possibly memory corrup-
tion, and hopefully segmentation faults at runtime.
For simple hacks with a couple of source and header files, we can afford to just
clean and rebuild any time we change header files significantly, for larger programs
however, we’d really like to have our build system track dependencies due to header
file inclusion too.
Make can’t possibly know what constitutes a dependency, and include a syntactic
analyzer to detect it, for each and every language, which is why this doesn’t happen
automatically. However with the help of our compiler’s pre-processor, we can create
the necessary rules to do it.
We’ll write a wildcard rule to generate makefile fragments with an explicit rule for
each source file in our project, which also includes header files in the dependency list.
Then we’ll instruct make to include these makefile fragments in our makefile as if we
wrote them by hand. Any missing makefile fragments will be automatically generated
with our wildcard rule during this inclusion. Finally we’ll add a rule to clean all these
dependency files (the makefile fragments).
src = $(wildcard *.c)
obj = $(src:.c=.o)
dep = $(obj:.o=.d) # one dependency file for each source
mygame: $(obj)
$(CC) -o $@ $^ $(LDFLAGS)
.PHONY: clean
clean:
rm -f $(obj) mygame
.PHONY: cleandep
cleandep:
rm -f $(dep)
The added lines are marked with comments in the example above. To see exactly
what the C preprocessor outputs when instructed to write dependency information, try
running the following in a file with a single include statement:
That’s right, it just outputs a single makefile rule! Which is exactly what we need
for make to track all the dependencies correctly.
7
6.5 Building sub-projects
Consider the following case: our game has source files in src/, but we also need to
use an external library called libfoo, which is not a system-wide installed library, but
one we want to carry with our source tree under libs/foo. How would we integrate
that in our build system?
Very simply, instruct make to first build libfoo, by running make after changing
to the libs/foo directory, and then link it as usual:
.PHONY: clean
rm -f $(obj) mygame
By adding the phony libfoo rule as a dependency to our binary linking rule, we
essentially force make to always change into libs/foo and run make before linking
our binary. If there is no need to actually rebuild libfoo because it has no changes,
that makefile will do nothing and return immediately, so there’s no harm in trying every
time.
Arbitrarily complex programs can be built by using a similar approach, to build
various modules as separate libraries. So this little snippet really scales our simple
build system to easily handle huge projects as well.
8
PREFIX = /usr/local
.PHONY: install
install: mygame
mkdir -p $(DESTDIR)$(PREFIX)/bin
cp $< $(DESTDIR)$(PREFIX)/bin/mygame
.PHONY: uninstall
uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/mygame
Special care should be taken when writing install and uninstall rules, because these
are necessarily executed with root privileges by the user, when installing system-wide.
Avoid wildcards in rm commands like the plague.
Note that I also concatenated another variable in front of the PREFIX, called
DESTDIR. This variable is most of the time undefined (and thus the empty string),
and has no effect whatsoever; it’s use is to allow for staging installations to tempo-
rary directories before manually moving them to their actual place, and it’s commonly
required by software packaging and distribution systems.
So for instance running this with: make DESTDIR=/tmp/stage install
will result in our program being installed under /tmp/stage/usr/local/bin,
where it can be inspected and then moved safely by the packaging software to the cor-
rect place. This becomes much more important when dealing with installing libraries,
which generally contain a lot more of files going many different places in the filesys-
tem.
$(alib): $(obj)
$(AR) rcs $@ $^
.PHONY: install
install: $(alib)
mkdir -p $(DESTDIR)$(PREFIX)/lib
mkdir -p $(DESTDIR)$(PREFIX)/include
cp $(alib) $(DESTDIR)$(PREFIX)/lib/$(alib)
cp foo.h $(DESTDIR)$(PREFIX)/include/
.PHONY: uninstall
uninstall:
rm -f $(DESTDIR)$(PREFIX)/lib/$(alib)
9
rm -f $(DESTDIR)$(PREFIX)/include/foo.h
prefix=/usr/local
debugsym=true
--enable-debug)
debugsym=true;;
--disable-debug)
debugsym=false;;
--help)
echo ’usage: ./configure [options]’
echo ’options:’
echo ’ --prefix=<path>: installation prefix’
echo ’ --enable-debug: include debug symbols’
echo ’ --disable-debug: do not include debug symbols’
echo ’all invalid options are silently ignored’
exit 0
;;
esac
done
Then Makefile.in can contain things like CFLAGS = $(dbg), and also use
the PREFIX variable as usual. The user can then use the conventional build & install
commands, and our makefile will be able to act accordingly:
10
./configure --prefix=/usr/local --disable-debug
make
sudo make install
11