Makefile tutorial 2.0 2021-06-16
The new and improved Makefile tutorial, for beginners and intermediates alike. Topics covered include pattern matching, compiling from and to different directories, header dependencies, and variables.
Translations: English | français | Português (Brasil)
- Basic POSIX shell proficiency. Including environment variables.
- Basic GCC arguments. Including
- Basic C, knows the difference between an executable, object files, source files, and header files.
It’s highly recommend for anyone reading this to follow along. Get started by cloning this repo:
git clone https://noahloomans.com/tutorials/makefile
Getting a "certificate has expired" error?
42 Network iMacs currently ship with an outdated list of certificate authorities, causing the clone to fail.
You can clone from this alternative URL instead for the time being:
git clone https://gitlab.com/nloomans/noahloomans.com-tutorials-makefile
This repository includes the following
foo: main.o greeter.o gcc -o foo main.o greeter.o main.o: main.c gcc -c -Wall -Wextra -Werror -o main.o main.c greeter.o: greeter.c gcc -c -Wall -Wextra -Werror -o greeter.o greeter.c
This Makefile consists of 3 rules. A rule consists of a target, any amount of prerequisites, and finally a recipe. The syntax is as follows:
target: prerequisites recipe
A rule describes how
make can make something. The first rule with a target
foo describes how we can link together our executable. The second & third
rules describe how to compile the individual object files.
When you run
make foo, make will look for a rule which can compile
this case the first rule. Before executing the recipe, make will first figure
out if the recipe needs to be run at all, and if any other rules need to be
executed first. Make first checks if every individual prerequisite exists and
main.o prerequisite will not exist on the first run. Make will look
through the rules to find one with a compatible target. In this case this is
main.o rule. For this rule, the same process will start over again, as if
make main.o. Make sees that
main.c exists and
main.o does not. It
will therefor compile
main.o by executing the recipe. If however,
were to already be present, then make would compare the edit dates. If
is more recent than
main.o, make will act as if
main.o does not exist, and
On the first run, the same thing would happen for
Exercise: Compile the
foobinary using make. Then compile it again. Now, change
main.ca bit and compile
fooagain. Take note of what commands make executes.
Question: You ran
make foo3 times, during which instances did
Wrong! Make only executes a recipe if either of the following conditions is matched:
- There exists no file called target
- target exists, but one of the prerequisites is newer
During the first instance, all recipes will be executed. During the second
instance, no recipe will be executed as all targets already exist and all
prerequisites are older than the targets. During the third instance, only
foo got compiled. Because
greeter.o was newer than
no attempt to recompile
greeter.o was made.
Variables and pattern matching
Writing a new rule for every object file is cumbersome and error-prone. One
can write the previous
Makefile much cleaner like this:
foo: main.o greeter.o gcc -o $@ $^ %.o: %.c gcc -c -Wall -Wextra -Werror -o $@ $^
This Makefile functions exactly1 the same as the first Makefile.
%.o rule will now handle the compilation of all object files. The
a pattern match. This is what the GNU Makefile manual says about pattern
matching, in 10.5.4 How Patterns Match:
A target pattern is composed of a ‘%’ between a prefix and a suffix, either or both of which may be empty. The pattern matches a file name only if the file name starts with the prefix and ends with the suffix, without overlap. The text between the prefix and the suffix is called the stem. Thus, when the pattern ‘%.o’ matches the file name test.o, the stem is ‘test’. The pattern rule prerequisites are turned into actual file names by substituting the stem for the character ‘%’. Thus, if in the same example one of the prerequisites is written as ‘%.c’, it expands to ‘test.c’.
That was quite a blob of text, let’s give an example. When make tries to
search for a rule to compile
main.o with, it will match the pattern. The
main, is then injected in
%.c to become
main.c. Effectively, make
will pretend that the target is
main.o and the prerequisite is
The same is true for
greeter.o, and any other file ending in
.o. Note that
% is not a preprocessor statement. This all happens during runtime, and
therefor also works on files which have never been referenced in the Makefile.
The new version of our Makefile also contains two so-called
automatic variables. Namely
$^. These variables allow us to write
generic recipes that work with pattern matched rules.
$@ resolves to the
$^ resolves to all of the prerequisites.
Question: There is an additional
stray.cincluded in the example repo. Using the above Makefile, what would happen if we were to execute
% is not a preprocessor statement. When you write
make stray.o, make will check each rule, one by one, to see if they can make
stary.o. The rule with a target of
%.o is able to make
stray.o will be compiled, the
foo rule will never be
foo rule will only be used by make if you explicitely write
make foo, or if you just write
make on its own, which causes make to use the
Until now we wrote things like the list of object files and which flags gcc takes inline. This is fine for small Makefiles, but let’s clean it up a bit:
NAME := foo OBJFILES := main.o greeter.o LDFLAGS ?= CFLAGS ?= -Wall -Wextra -Werror $(NAME): $(OBJFILES) $(CC) $(LDFLAGS) -o $@ $^ %.o: %.c $(CC) -c $(CFLAGS) -o $@ $^
The variables set with
:= are always set unconditionally. Variables set with
?= are only set if it hasn’t been set already. Make inherits variables from
environment variables. This allows us to easily run make with custom
by, for example, running the following in your shell:
# Compile the program in debug mode (-g), and don't stop compiling on # warnings. CFLAGS="-Wall -Wextra -g" make
CC variable is a bit special in that it is pre-defined to
cc. The idea
is simple, if you write standards-compliant code, why should you specify with
which compiler to compile your code with?
cc is the default C compiler on most
systems, and if the user wants your program to be compiled with a different
compiler, they can set the
# Use the clang compiler and compile with the clang specific # -fsanitize=address option to detect memory corruption. export CC=clang export CFLAGS="-Wall -Wextra -g -fsanitize=address" export LDFLAGS="-g -fsanitize=address" make
Note: You may also see the
=operator being used in Makefiles you find in the wilderness. This is also correct, but functions in a slightly different way from
:=. For the differences, see the official documentation.
With the Makefiles used until now, all source files and object files are littered in the root of the repository. One can make a repository a lot more organized by putting them in separate folders.
Let’s take a look at our object file rule again:
%.o: %.c $(CC) -c $(CFLAGS) -o $@ $^
% pattern doesn’t just work on suffixes, it works on prefixes as well. We
can use this to compile our object files to a different folder from our source
# Compile object files in the obj folder from source files in the src # folder. obj/%.o: src/%.c $(CC) -c $(CFLAGS) -o $@ $^
Note that make has no knowledge of directories here. All it does is look for
“obj/” at the start and “.o” at the end, and replaces those with “src/” at the
start and “.c” at the end. Because the
$^ automatic variables
resolve to the processed forms, something like this will be executed:
cc -c -Wall -Wextra -Werror -o obj/example.o src/example.c
This won’t quite work however, as the compiler won’t create an
obj folder if
it doesn’t already exist. We can address this by running
mkdir -p before
which creates a directory if it does not already exist. To figure out which
directory to create, one can use the
$(dir ...) function.
obj/%.o: src/%.c @mkdir -p $(dir $@) $(CC) -c $(CFLAGS) -o $@ $^
@ at the start is just a little aesthetic choice. It makes it so that all
mkdir calls won’t be printed. The output can be quite cluttered
Now to make this all work, we just need to make sure that anywhere we use an
object file as a prerequisite we reference them with their
otherwise make won’t be able to find them. Our
Makefile now looks like this:
NAME := foo OBJFILES := obj/main.o obj/greeter.o LDFLAGS ?= CFLAGS ?= -Wall -Wextra -Werror $(NAME): $(OBJFILES) $(CC) $(LDFLAGS) -o $@ $^ obj/%.o: src/%.c @mkdir -p $(dir $@) $(CC) -c $(CFLAGS) -o $@ $^
Further reading: You can avoid writing the
obj/prefix every time using 6.3.1 Substitution References.
Question: Imagine that all of our
.cfiles had an
ft_prefix, and were in the
srcfolder. So our main source file would be
src/ft_main.c. How can we make object files for them without the
ft_prefix? So that
src/ft_main.cwould get compiled to the
Wrong! If you tell
cc to compile from the
ft_$^, it will end up trying
to use a file like
ft_src/example.c. The file used as input should always
be the same file as listed in the prerequisite.
Wrong! The bit before the colon is the target. The file we are compiling
ft_ should be part of the source file, which is listed after the
colon. When you try to make the
obj/main.o file, make will match that with the
obj/%.o rule. The stem will be
main, which make then inserts into the
prerequisite. So if we have
src/ft_%.c as our prerequisite, make insert
the stem into that and look for
Until now, all of our Makefile rules actually make something, as defined by the
target. This is almost always what we want, but there are a few exceptions.
make clean for example. In most Makefiles2, such a rule will cleanup
all the files the Makefile created. This is what a naïve implementation would
NAME := foo OBJFILES := obj/main.o obj/greeter.o LDFLAGS ?= CFLAGS ?= -Wall -Wextra -Werror $(NAME): $(OBJFILES) $(CC) $(LDFLAGS) -o $@ $^ obj/%.o: src/%.c @mkdir -p $(dir $@) $(CC) -c $(CFLAGS) -o $@ $^ clean: rm -f $(NAME) $(OBJFILES)
The issue with this is that make will now think that our
clean rule has a
target file named
clean. Let me illustrate why is this a problem:
$ touch clean $ make clean make: 'clean' is up to date.
Make did not want to execute the
clean rule, because a file named
already exists. To fix this, we need to declare the
clean target to be a
phony. Make has a magic
.PHONY target, all the prerequisites of this
target are marked as phonies, and make will no longer check them against any
file with the same name. Instead make will assume phonies are always out of
date, and need to be recompiled. This gives us the following
NAME := foo OBJFILES := obj/main.o obj/greeter.o LDFLAGS ?= CFLAGS ?= -Wall -Wextra -Werror $(NAME): $(OBJFILES) $(CC) $(LDFLAGS) -o $@ $^ obj/%.o: src/%.c @mkdir -p $(dir $@) $(CC) -c $(CFLAGS) -o $@ $^ clean: rm -f $(NAME) $(OBJFILES) .PHONY: clean
Question: Imagine that our
.PHONYrule was defined like this:
.PHONY: clean $(NAME). We run
maketwice. What would happend the second time?
foo is relinked, it is not recompiled as
applies to the rules directly referenced, and not the prerequisites of those
$(NAME) is a prequisite of
.PHONY, make always considers
it out-of-date, and is therefore relinked everytime we run
There are two more things which are nice to have in our Makefile. First of,
right now recompiling our project using
make won’t quite work if you modified
a header file, as make is unaware of those.
This easiest way is to tell make that every object file also depends on every header file. That would look like this:
NAME := foo HEADERFILES := src/greeter.h OBJFILES := obj/main.o obj/greeter.o LDFLAGS ?= CFLAGS ?= -Wall -Wextra -Werror $(NAME): $(OBJFILES) $(CC) $(LDFLAGS) -o $@ $^ obj/%.o: src/%.c $(HEADERFILES) @mkdir -p $(dir $@) $(CC) -c $(CFLAGS) -o $@ $<
We made three changes. First we added a
HEADERFILES variable which contains a
list of header files, then we added it as a prerequisite to the
rule, and finally, we changed the second
$^ to a
obj/%.o rule now has multiple prerequisites: The corrosponding
file and all header files defined in
HEADERFILES. Because make checks if any
prequisite is newer than the target, the target will be recompiled
whenever a header file updates. This same check will be executed for every
object file rule, causing all of them to be recompiled.
$< automatic variables are different in a subtile but
$^ resolves to all of the prerequisites, while
resolves to the first one. In our case, that means that
$< ignores the header
files and just expands to our source file. This is exactly what we want since
the our compiler shouldn’t be told what the header files are directly.
That was it, I hope you found my Makefile tutorial useful! Big thanks to Pepijn Holster for helping me write this, and thanks to everyone who helped me proof read.
Want to translate this tutorial? I will gladly accept translations! Shoot
me an email at email@example.com, or DM me on Slack at
nloomans if you
are a student who is part of the 42 Network. I’ll help you get started.