Make Concepts¶
There are many resources out there for how to use make and some of them are very good. However, they mostly focus on details of syntax and how to spell the different text manipulation functions. This document is not that. This document is about why Make is the way it is, and how to help it do its job.
What Is Make Doing?¶
At its core, all Make does is build up a DAG of stuff you want and the stuff required to generate the stuff you want. Make only knows about three things, and two of them are the same:
Inputs
Outputs
Recipes
Inputs and outputs are just files. Make looks at the modification time for a target (that’s an output) and all of its requirements (inputs) and decides whether or not the target needs rebuilding. Of course, this process happens recursively because, since inputs are files, they can also be outputs.
Doing Math¶
For example, let’s say you want to do some simple geometry. Anyone who’s made it through a geometry class knows that the formula for the area of a circle is \(a=\pi r^2\).
If we pretend the parts of that equations are files, and the contents of those files contain their values, we can break it up into some inputs and outputs:
This is a good example because we have an ultimate target that we care
about, area
, an input we can tinker with, \(r\), a constant
(\(\pi\)) and an intermediate result \(r^2\).
Recipes¶
So we’ve covered the first two things Make knows about, but there’s also the third thing: Recipes. A recipe tells Make how to create a file with the same name as the target. A little bit of Make’s syntax might help clarify here, but then I’m going to repeat myself.
target: dependencies
recipe
A recipe is just a script that Make runs, expecting it to create or
update the target it’s trying to build. Make has a special name for
the current target, $@
, which is unfortunately pretty
unintuitive. However, it’s super common so it’s best to just memorize
it. If you see $@
in a recipe, it will be expanded to be the name of
the thing we’re trying to build. A trivial Makefile might be:
filename:
echo "hello make" > $@
You can put that in a file called Makefile, run make filename
and
this will happen:
$ make filename
echo "hello make" > filename
$ cat filename
hello make
Alright, so now let’s go back to that area of a circle example from before. We’ve got the dependencies all sketched out, and I’ll hack up some Python 1-liners to spit out files with appropriate values:
pi:
@echo "Calculating pi"
@python3 -c "import math; print(math.pi)" > $@
r-squared: r
@echo "Calculating r^2"
@python3 -c "print(`cat r` * `cat r`)" > $@
area: r-squared pi
@echo "Calculating area"
@python3 -c "print(`cat r-squared` * `cat pi`)" > $@
Ok, so I used a bit of extra @
s to hide the commands from the
command line, but now we can make area:
$ make -f area.mk area
make: *** No rule to make target 'r', needed by 'r-squared'. Stop.
Whoops, there’s no way for Make to know how to build r
. That comes
from me. If I stuff a value in a file named r
and try again, this
is what happens:
$ echo 2.345 > r
$ make -f area.mk area
Calculating r^2
Calculating pi
Calculating area
$ cat area
17.275696541906612
We did it! We put the area of a circle with radius 2.345 into a file called area.
No More!¶
That’s it. That’s all you need to really understand what Make is doing. There are extra wrinkles, like how you can have multiple targets generated by the same recipe:
target1 target1: dependency1 dependency2
script that generates both target1 and target2
There are also other useful builtin variables like $^
(all the
dependencies) and $<
(the first dependency). There are also a
whole bunch of functions for manipulating file names and lists of
strings, which are described
in the manual. But if you’ve understood everything so far, you should
be able to understand almost anything you see in a well-written
Makefile. Well, after reading about patterns
in the manual.
Oh, and indentation is done with actual tab characters. It’s just the rule.