Makefile Syntax โ€” Rules, Targets, and Recipes

Lesson 2: Makefile Syntax โ€” Rules, Targets, and Recipes

Every Makefile is built from rules. A rule says: this target depends on these prerequisites, and here's how to build it. That's the entire syntax, and it's compact enough to learn in one sitting.

The Anatomy of a Rule

target: prerequisites...
<TAB>recipe line 1
<TAB>recipe line 2

Three parts:

  • Target โ€” the file (or named action) this rule produces.
  • Prerequisites โ€” files that must exist and be up to date before the target is built.
  • Recipe โ€” shell commands, each on its own line, indented with a TAB character.
The classic gotcha: recipe lines must start with a TAB, not spaces. If you indent with spaces, Make fails with *** missing separator. Stop. This trips up everyone at least once โ€” configure your editor to insert a literal tab in Makefiles.

Your First Makefile

Save this as Makefile:

hello.txt: hello.in
	cp hello.in hello.txt

Run make and you'll see:

$ make
cp hello.in hello.txt

Make ran the recipe because hello.txt didn't exist yet. Run it again:

$ make
make: 'hello.txt' is up to date.

Because hello.txt is now newer than hello.in, there's nothing to do. This is the incremental-build behavior from Lesson 1, and you just witnessed it.

Multiple Rules and the Default Goal

Makefiles usually define many rules. The first target in the file is the default goal โ€” what plain make builds:

all: hello.txt report.md

hello.txt: hello.in
	cp hello.in hello.txt

report.md: data.csv
	python3 render_report.py data.csv report.md

Here all has no recipe of its own โ€” it just groups prerequisites, so make builds both hello.txt and report.md. You can still build one specifically: make hello.txt.

Phony Targets: Actions That Aren't Files

Some targets don't produce a file at all โ€” they're actions. clean is the classic example:

.PHONY: clean
clean:
	rm -f hello.txt report.md

.PHONY tells Make: "this target doesn't correspond to a real file, so always run its recipe โ€” don't check timestamps." Without it, a stray file named clean would make make clean silently do nothing. Declare every action-style target as .PHONY.

Comments and Command-Line Basics

# starts a comment. And the make command itself has a few flags you'll use constantly:

CommandWhat it does
makeBuild the default goal (first target).
make targetBuild a specific target (e.g. make clean).
make -nDry run โ€” print commands without executing them.
make -BForce โ€” rebuild everything regardless of timestamps.
make -f mymakeUse a file with a different name.
make -C subdirChange directory first, then run Make there.
make -j4Run up to 4 recipes in parallel (Lesson 7).
target file to produce prerequisites prerequisites recipe commands

Rules With Multiple Targets

One rule can produce several files at once โ€” useful when a single command generates multiple outputs:

output.a output.b: input.txt
	split_input input.txt

Make treats this as a shorthand for two rules sharing the same recipe. If either output is missing, both are rebuilt.

Key idea: A rule with no prerequisites and a recipe is just "run this command" (clean). A rule with prerequisites and no recipe is just "make sure these exist" (all). The language is small; the power comes from composing rules into graphs.

๐Ÿง  Knowledge Check

1. What must every recipe line in a Makefile start with?

2. When you run plain make, which target gets built by default?

3. What does declaring a target as .PHONY do?

Further Reading