First Makefile

Last updated on 2026-10-07 | Edit this page

Estimated time: 40 minutes

Overview

Questions

  • How do I write a simple Makefile?

Objectives

  • Introduce key parts of a Makefile, rules, targets, dependencies and actions.
  • Run Make from the shell.
  • Edit a Makefile
  • Explain when and why to mark targets as .PHONY.

Save your work from the previous episode and checkout the branch for this episode:

BASH

$ git add report.tex
$ git commit -m "my work"
$ git switch -c my-02-branch remotes/origin/02-first-makefile
$ git ls-files

which should yield

OUTPUT

.gitignore
Makefile
README
abyss.txt
analyze.py
isles.txt
local.bib
report.tex

Look at the file called Makefile with

BASH

$ cat Makefile

OUTPUT

SHELL=bash

# First target, report.pdf, is the default.

report.pdf : local.bib report.tex abyss.pdf isles.pdf results.tex abyss.head
	latexmk -pdf report.tex

# Using a new feature of gnu-make as of version 4.3 that permits making
# several targets with one rule.  See
# https://www.gnu.org/software/make/manual/make.html#Multiple-Targets

abyss.dat abyss.pdf abyss.two &: analyze.py abyss.txt
	python analyze.py abyss.txt abyss.dat abyss.pdf |tee abyss.two

isles.dat isles.pdf isles.two &: analyze.py isles.txt
	python analyze.py isles.txt isles.dat isles.pdf |tee isles.two

results.tex: abyss.two isles.two
	@echo Create $@ with an editor from $^ ; exit 1

abyss.head: abyss.dat
	@echo Write a rule in the Makefile to create $@ from $^ ; exit 1

This is a build file, which for Make is called a Makefile - a file executed by Make. All of the indentation in this Makefile consists of TABs. It is unfortunate that while the difference between TABs and multiple SPACEs is invisible, the difference is significant for the program make.

If we try to run make we get

BASH

$ make

OUTPUT

python analyze.py abyss.txt abyss.dat abyss.pdf |tee abyss.two
top_two=[4044, 2807]
python analyze.py isles.txt isles.dat isles.pdf |tee isles.two
top_two=[3822, 2460]
Create results.tex with an editor from abyss.two isles.two
make: *** [Makefile:19: results.tex] Error 1

Let us talk about what happened.

If make is invoked without arguments, it uses the default name Makefile for instructions. In Makefile, since the first target is report.pdf, make will try to build that. Now we will talk about each of the elements of the rule

report.pdf : local.bib report.tex abyss.pdf isles.pdf results.tex abyss.head
	latexmk -pdf report.tex
  • report.pdf is the target, the file to be created, or built.
  • A colon, :, separates targets from dependencies.
  • latexmk -pdf report.tex is the action that will be executed once the dependencies exist.
  • report.tex and local.bib are the first two dependencies, files that are needed by the action to update the target. Targets can have zero or more dependencies. Those two dependencies already existed and were listed by git ls-files.
  • abyss.pdf and isles.pdf are the third and fourth dependencies. Since they did not exist, make invoked the second and third rules in the Makefile to build them using analyze.py.
  • results.tex, the fifth dependency did not exist. So make invoked the fourth rule. The action for the fourth rule (on line 19 of the Makefile) has a return value of 1 which indicates an error. So when it is invoked make terminates and reports the error.
  • Together, the target, dependencies, and actions form a rule.

The dependencies results.tex and abyss.head don’t exist, and the rules in the Makefile to build them return errors.

Callout

Dependencies

The order of rebuilding dependencies is arbitrary. You should not assume that they will be built in the order in which they are listed.

Dependencies must form a directed acyclic graph. A target cannot depend on a dependency which itself, or one of its dependencies, depends on that target.

Let’s create results.tex from the results of running analyze.py with a text editor and create abyss.head with

BASH

$ head -n 10 abyss.dat |awk '{print $1, $2}' > abyss.head

Checking the files we find:

BASH

$ cat abyss.head

OUTPUT

the 4044
and 2807
of 1907
a 1594
to 1515
in 1221
i 974
was 695
it 680
for 675

BASH

$ cat result.tex

OUTPUT

Book & First & Second & Ratio\\ \hline
abyss & 4044 & 2807 & 1.44 \\
isles & 3822 & 2460 & 1.55

Finally we can build and view the pdf document with

BASH

$ make
$ evince report.pdf

In the next episode, we will restructure the Makefile and the python code to automate building result.tex. But before that, we will make some easy changes.

First, put the command we used to build abyss.head into the Makefile. The modified rule should be

abyss.head: abyss.dat
	head -n 10 abyss.dat |awk '{print $$1, $$2}' > abyss.head

Make maps the double $$ to a single $ before executing the action. We can verify that with

BASH

$ rm abyss.head
$ make abyss.head

OUTPUT

head -n 10 abyss.dat |awk '{print $1, $2}' > abyss.head

Next add the following block to Makefile

clean:
	rm -f abyss.head *.pdf *.dat *.two *.aux *.bbl *.blg *.fdb_latexmk \
*.fls *.log
	touch clean

Now try the following sequence

BASH

$ make clean
$ make
$ touch results.tex
$ make

Try again

BASH

$ make clean

OUTPUT

make: 'clean' is up to date.

Change the block to

.PHONY: clean
clean:
	rm -f abyss.head *.pdf *.dat *.two *.aux *.bbl *.blg *.fdb_latexmk *.fls *.log
	touch clean

and test with

BASH

$ make clean
$ make clean
$ make clean

Finally change the block to

.PHONY: clean
clean:
	rm -f abyss.head *.pdf *.dat *.two *.aux *.bbl *.blg *.fdb_latexmk *.fls *.log
Callout

“Up to Date” Versus “Nothing to be Done”

If we ask Make to build a file that already exists and is up to date, then Make informs us that:

BASH

$ make isles.dat

OUTPUT

make: `isles.dat' is up to date.

If we ask Make to build a file that exists but for which there is no rule in our Makefile, then we get message like:

BASH

$ make analyze.py

OUTPUT

make: Nothing to be done for `analyze.py'.

up to date means that the Makefile has a rule with one or more actions whose target is the name of a file (or directory) and the file is up to date.

Nothing to be done means that the file exists but either :

  • the Makefile has no rule for it, or
  • the Makefile has a rule for it, but that rule has no actions

Finally, if we ask Make to build a file that doesn’t exist and for which there is no rule in our Makefile we get a self explanatory message like this

BASH

$ make foo

OUTPUT

make: *** No rule to make target 'foo'.  Stop.
Callout

Makefiles as Documentation

By explicitly recording the inputs to and outputs from steps in our analysis and the dependencies between files, Makefiles act as a type of documentation, reducing the number of things we have to remember.

Callout

Makefiles Do Not Have to be Called Makefile

We don’t have to call our Makefile Makefile. However, if we call it something else we need to tell Make where to find it. This we can do using -f flag. For example, if our Makefile is named MyOtherMakefile:

BASH

$ make -f MyOtherMakefile

Sometimes, the suffix .mk will be used to identify Makefiles that are not called Makefile e.g. install.mk, Rules.mk etc.

When it is asked to build a target, Make checks the ‘last modification time’ of both the target and its dependencies. If any dependency has been updated since the target, then the actions are re-run to update the target. Using this approach, Make knows to only rebuild the files that, either directly or indirectly, depend on the file that changed. This is called an incremental build.

Key Points
  • Use # for comments in Makefiles.
  • Write rules as target: dependencies.
  • Specify update actions in a tab-indented block under the rule.
  • Use .PHONY to mark targets that don’t correspond to files.