Writing Makefile
s is like writing a recipe.
It consists of ingredients and a list of procedures to turn those ingredients into a product.
In this tutorial, I will briefly cover how this works.
We’ll first look at how we might turn a recipe for an apple pie from a cookbook into a Makefile
.
Making an apple pie
A recipe to cook an apple pie1 might look as follows:
Ingredients
- 200 grams of butter
- 20 grams of sugar
- 400 grams of self-raising flower
- egg
- 8 grams of vanilla sugar
- salt
- 1 Kg apples
- 75 grams of raisins
- cinnamon
Making the pie
Melting the butter
- Put the butter in a microwave and slowly melt it, stir it once in a while
Making the dough
Prequisites: melting the butter
- Mix the molten butter, the flower and 2/3s of the egg
- Knead the dough until it’s consistent
Preparing the raisins
- Put the raisins in lukewarm water
- Wait 15 minutes
- Dry them off
Preparing the apples
- Skin the apples
- dice them
Making the filling
Prequisites: preparing the raisins, preparing the apples
- Mix the diced apples with the sugar and the prepared raisins
Making the pie
Prequisites: Making the dough, making the filling
- Cover the springform using 2/3s of the dough
- Put the filling in the pie
- Cover the pie with the remainder of the dough
Baking the pie
Prequisites: Making the pie
- Put the pie in the oven that’s been pre-heated to 200 degrees Celcius
- Take it out after 60 minutes
I’ve split this recipe up into more steps than you may be used to. I did this to illustrate that you have certain parts of the process of building an apple pie product that you can separate. These parts may have dependencies on prior parts of the process, though. For example, you can not make the filling before you prepared the apples and the raisins.
Turning the recipe into a Makefile
A Makefile
also consists of (intermediate) products, recipes and prequisites.
It has the following structure:
product: ingredient1 ingredient2 prequisite1
recipe that combines prequisite1 with ingredient1 and ingredient2
prequisite1: ingredient3 ingredient1
recipe for prequisite1
make
can use these rules to create product
for you.
It will also figure out whenever it needs to do something again, mainly if an ingredient or a prequisite changed.
So now let us change our apple pie recipe into something make
would be able to understand:
pie: prepared_pie
put prepared_pie into oven
wait 60 minutes
take it out
prepared_pie: dough filling
cover springform with 2/3s of dough
put filling in
close off pie with the remainder of the dough
filling: sugar prepared_raisins prepared_apples
Mix the apple with the sugar and the prepared raisins
prepared_raisins: raisins
Put the raisins in lukewarm water
Wait 15 minutes
Dry them off
prepared_apples:
Skin the apples
Dice the apples
dough: molten_butter flower egg
Mix the molten butter, the flower and the egg
Knead until consistent
molten_butter: butter
Put the butter in the microwave on low until it's molten
- Note: I put
pie:
at the top, becauseMake
will try to build the top-most target (product). Otherwise the order is not important.
Moving to code
In our apple pie Makefile
we can identify that we have different (intermediate) products or targets that have some steps associated to prepare them,
and some of these depend on others or on ingredients.
We specify this so that Make can figure out when it needs to update something.
Of course, “updating sugar” makes no sense, so lets look at an example where we have a source file.
A simple example
/tmp/src $ ls
hello.c
#include<stdio.h>
void say_hello() {
puts("Hello, world!");
}
int main(int argc, char** argv) {
say_hello();
}
We could build this using the following gcc
shell command:
/tmp/src $ gcc -o hello hello.c
/tmp/src $ ./hello
Hello, world!
But lets write a simple Makefile
.
We are lazy and want to be able to just type make
.
/tmp/src $ touch Makefile # Should have a capital M!
/tmp/src $ your-editor-here Makefile # e.g. vim, gedit or nano
Then we enter the following as our build recipe:
hello: hello.c
gcc -o hello hello.c
Important: Makefile
rules must be indented with tabs, not spaces. Make sure you configure your editor correctly!
Lets see if this works:
/tmp/src $ make
make: 'hello' is up to date.
Huh? make
didn’t do anything!
It turns out that make
is quite smart, and it realised that hello
has a more recent modification date than hello.c
.
That usually means that there’s no good reason to do all that work of compiling hello.c
again, so make
just doesn’t do it.
We can force it to build things using make -B
, or we can remove hello
. Let us do that:
/tmp/src $ rm hello
/tmp/src $ make
gcc -o hello hello.c
/tmp/src $ ./hello
Hello, world!
Neat!
A more complicated example
So far, our example was very basic. make
shines when you have a more complicate build process.
Lets extend our example to have more than one source file, which we want to build and link separately.
This allows us to only recompile what has been changed, while we are developing the project.
Let us set up two source files in our /tmp/src
folder:
// hello.c
// we use extern to indicate that we will specify say_hello() elsewhere.
extern void say_hello();
int main(int argc, char** argv) {
say_hello();
}
// say_hello.c
#include <stdio.h>
void say_hello() {
puts("Hello, world! from say_hello.c");
}
We can build and link these files as follows
/tmp/src $ gcc -c -o hello.o hello.c
/tmp/src $ gcc -c -o say_hello.o say_hello.c
# Note that in the next line we use the compiled `.o` files!
/tmp/src $ gcc -o hello hello.o say_hello.o
/tmp/src $ ./hello
Hello, world! from say_hello.c
Clearly, this is way too much work to type.
We can write a Makefile
to save us all this work.
hello: hello.o say_hello.o
gcc -o hello hello.o say_hello.o
hello.o: hello.c
gcc -c -o hello.o hello.c
say_hello.o: say_hello.c
gcc -c -o say_hello.o say_hello.c
We made three rules: one to build the final target hello
, and two to build its prequisites hello.o
and say_hello.o
.
Lets test our Makefile
:
/tmp/src $ make
make: target 'hello' is up to date.
Oh, right… We got outsmarted by make
s lazyness again. Lets force make
to rebuild everything.
/tmp/src $ make -B
gcc -c hello.c -o hello.o
gcc -c say_hello.c -o say_hello.o
gcc -o hello hello.o say_hello.o
/tmp/src $ ./hello
Hello, world! from say_hello.c
That worked great!
We can now also see that make
is smart, if we change for example say_hello.c
.
Lets change say_hello()
:
void say_hello() {
puts("Hello, world! from say_hello.c");
puts("And goodbye again");
}
Now we can run make
again:
/tmp/src $ make
gcc -c say_hello.c -o say_hello.o
gcc -o hello hello.o say_hello.o
make
realised that only say_hello.c
changed and only rebuild say_hello.o
.
As a result of that, it also needed to link hello
again, but it skipped building hello.o
!
It’s easy to see that as your program grows, this can be very helpful.2
Common problems and caveats
Finally, lets discuss some of the problems you may run into.
Makefile template
As a reminder, your Makefile
should look as follows:
target: ingredients
build rules indented with **TAB**
target
should normally be something on the filesystem, unless you want to always have it execute when you try to build it.
Don’t forget the :
after target
.
ingredients
should be other target
s or files on the filesystem, and it should be all on one line.
Again: the indentation should be with tabs.
Indentation
Makefile
s must be indented with tabs. If you indent them with spaces you may get errors
Makefile: 2: *** missing separator. Stop.
Already up to date
make
is lazy and sees that your binaries are more recent than your source files.
If you want to force a rebuild, use make -B
, change the source files, or remove the targets.
No rule to make target
If you specify something that make
can not find on the filesystem or does not know how to build from other things, it will complain.
For example:
say_hello.o: zay_hello.c
gcc -c say_hello.c -o say_hello.o
We made a typo in say_hello.c
when we specified the ingredients for say_hello.o
.
This results in the error:
make: *** No rule to make target 'zay_hello.c', needed by 'say_hello.o'. Stop.
We can fix this by repairing the typo.
This can also apply to targets that depend on other targets:
hello: hello.o zay_hello.o
gcc -o hello hello.o say_hello.o
Then make
will also complain:
make: *** No rule to make target 'zay_hello.o', needed by 'hello'. Stop.
We can solve this by writing a rule for zay_hello.o
, that renames say_hello.o
to zay_hello.o
:
zay_hello.o: say_hello.o
mv say_hello.o zay_hello.o
But that is a bit silly. Better fix the typo, in this case.
No targets specified and no makefile found.
This error occurs if you don’t have a Makefile
in your current folder.
Check if a Makefile
exists, the name is spelled correctly (exactly Makefile
) and make sure it has a capital M
!
The wrong thing is built!
make
, without options, builds the first target it finds.
You can build a specific target by typing make target
, or you can move around the rules.
A common trick is adding a target all
to the top:
all: mything
#[..]
mything: ...
...
Suggestions
If you have suggestions for further improvement, find the source here and send in a pull request.