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:

digraph area {
    "pi" [label=< &#960; >]
    "pi" -> "area"
    "r2" [label=< r<sup>2</sup> >]
    "r2" -> "area"
    r -> "r2"
}

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.