← All 135 books GNU Make, at work
One concept per page

GNU Make, at work

Twenty-eight lessons on GNU Make, one concept per page: a rule's anatomy of goal, prerequisites and recipe; how make decides what to rebuild from modification times and the dependency graph; variables, the two flavors of = and :=, ?= and +=, the environment and override; the automatic variables $@ $< $^, pattern rules, implicit rules and .PHONY; functions in the $(...) form, wildcard, patsubst, subst, filter and foreach; include and -include, recursive make with $(MAKE), MAKECMDGOALS and the @ - + prefixes; parallel builds with -j, order-only prerequisites, vpath and VPATH, double-colon rules; the missing separator and make's error grammar; and a first real Makefile for a small multi-file project, walked end to end.


Steve Hodgkiss 7 lessons

One concept, one page, one real capture. That's the rhythm.

GNU Make, at work

Twenty-eight lessons on GNU Make, one concept per page: a rule's anatomy of goal, prerequisites and recipe; how make decides what to rebuild from modification times and the dependency graph; variables, the two flavors of = and :=, ?= and +=, the environment and override; the automatic variables $@ $< $^, pattern rules, implicit rules and .PHONY; functions in the $(...) form, wildcard, patsubst, subst, filter and foreach; include and -include, recursive make with $(MAKE), MAKECMDGOALS and the @ - + prefixes; parallel builds with -j, order-only prerequisites, vpath and VPATH, double-colon rules; the missing separator and make's error grammar; and a first real Makefile for a small multi-file project, walked end to end.


Set in Space Grotesk, Inter and JetBrains Mono (SIL Open Font License).

Every fact in this book is as the official source states it, fetched and read during this build: the complete GNU Make manual from gnu.org (the definition of truth for this book) and the Wikipedia article on Make (software). The Wikipedia Makefile article is a redirect stub, not a source. No facts are sourced from Reddit, Stack Overflow or vendor blogs. Every command and output pair on every page was run on this machine (GNU Make 4.3 at /usr/bin/make, Debian, cc 12.2.0) and confirmed before printing; every build ran in a throwaway directory under a scratch tree, never inside a project or a shared tree. Where this machine's GNU Make 4.3 prints older wording than the manual's newer-version examples, such as the missing-separator hint added after 4.3, the page says the version out loud. An independent guide, not affiliated with or endorsed by the Free Software Foundation, the GNU Make maintainers, or the Wikimedia Foundation.

Your purchase is for personal use only. You do not have redistribution rights: please do not share, resell, or republish this book or its pages.

© 2026 Steve Hodgkiss. All rights reserved. Personal use only; no redistribution rights.

Edition 1.0 · stevehodgkiss.net

Contents

Contents


Part 1 · The rule itself
A rule's anatomy4
Part 2 · Variables and words
The environment5
Part 3 · Patterns and phony
.PHONY6
Part 4 · Functions in calls
patsubst and subst7
Part 5 · Makefiles in pieces
Recursive make8
Part 6 · Order and place
Parallel builds9
Part 7 · Breakage and a build
A real Makefile10

Per the GNU Make manual, a rule appears in the makefile and says when and how to remake certain files, listing the files that are the prerequisites and the recipe for updating; a rule tells make two things, when its goal is out of date and how to update it. This machine's capture: a three-rule makefile built a small C program with cc, printing each recipe as it ran.

GNU Make companion · No. 01
anatomy

A rule's anatomy

Goal, prerequisites, recipe

one rule, three partsapp: main.o util.o  cc -o app main.o util.opart           in this rulegoal           appprereqs        main.o util.oa rule appears in themakefile and says whenand how to remake files,the GNU Make manual'sopening definitionthe recipe line holds the shell command; the TAB before it is law

A rule says when and how to remake files: a goal, the prerequisites it needs, and a recipe of shell lines.

Our capture built a real program: cc -c main.c, then the link line. The recipe runs in a sub-shell, one line at a time.

Three parts, one page. Every later idea in this book is one of these three parts wearing a costume.

RUN IT THIS WEEK

Write a two-rule makefile by hand and run it once; name the three parts out loud as make runs.

Per the GNU Make manual, variables in make can come from the environment in which make is run: every environment variable becomes a make variable, but an explicit assignment in the makefile or with a command argument overrides the environment, and the -e flag flips that precedence, which the manual does not recommend. This machine's captures: MODE=night make reached a recipe; a makefile MODE = day won over the environment; MODE=night make -e let the environment win.

GNU Make companion · No. 02
environment

The environment

The shell hands variables over

the shell hands its variables over$ MODE=night makeMODE=night # seen, usedMODE=night makefile has MODE = day -> dayMODE=night make -e-> night # -e flips itHOME=/root # seen tooevery environmentvariable becomes amake variable; anexplicit makefileassignment overrides it,-e is not recommendedexport passes makefile variables down to recipes and sub-makes

Every environment variable becomes a make variable at startup. But an explicit makefile assignment overrides the environment.

Our captures: MODE=night make printed night; add MODE = day to the file and it printed day; only make -e handed the win back, a flag the manual calls not recommended.

Recipes also see your shell: HOME was visible in our capture without any makefile help.

RUN IT THIS WEEK

Print one variable from a recipe with and without a makefile assignment for it, then once with -e.

Per the GNU Make manual, a phony goal is one that is not really the name of a file; the prerequisites of the special goal .PHONY are always remade, and a phony goal avoids trouble when a real file with that name exists, since the file would otherwise satisfy the goal. This machine's captures: with a file named clean present, make clean said 'clean' is up to date and did nothing; with .PHONY: clean the recipe ran.

GNU Make companion · No. 03
phony

.PHONY

A goal that is not a file

a goal that is not really a file$ make cleanclean:  rm -f *.ofile named clean exists:'clean' is up to date..PHONY: clean -> rm runscleanfile?a phony goal is not thename of a file, so asame-named file cannotsilence itdeclare clean, all and friends phony and the file system stops voting

A phony goal is not really the name of a file. Declare it under .PHONY and make stops asking the file system for permission.

Our captures: with a junk file named clean, make clean refused, calling it up to date. One line, .PHONY: clean, and the rm ran.

clean, all, install-style helper goals: if no file should ever carry the name, mark it phony.

RUN IT THIS WEEK

Create a file named clean in a scratch project and feel the refusal, then add .PHONY: clean and rerun.

Per the GNU Make manual, $(patsubst pattern,replacement,text) finds whitespace-separated words in text that match pattern and replaces them with replacement, where % in the replacement stands for the matched text, and $(subst from,to,text) replaces every occurrence of from with to. This machine's captures: patsubst %.c,%.o over the source list produced the object list, and subst .c,.bak produced the backup list.

GNU Make companion · No. 04
patsubst

patsubst and subst

Rewrite every word

rewrite every word of a listOBJS := $(patsubst %.c,%.o,$(SRCS))-> extra.o main.o util.oCOPY := $(subst .c,.bak,$(SRCS))-> extra.bak main.bak ...main.c%.c to %.omain.opatsubst matches wholewords against a %pattern; subst swapsplain text anywheresources to objects in one call: the load-bearing line of most makefiles

$(patsubst %.c,%.o,list) rewrites each word that matches; the % carries the stem across. $(subst from,to,text) swaps plain text anywhere.

Our captures: sources became extra.o main.o util.o, then .bak names, in two calls.

Sources to objects in one call is the load-bearing line of half the makefiles on earth.

RUN IT THIS WEEK

Turn a SRCS list into OBJS with patsubst and echo both lists from one recipe line.

Per the GNU Make manual, recursive make commands should always use the variable $(MAKE), not the explicit command name, and -C changes to a directory before doing anything. This machine's capture: a recipe $(MAKE) -C sub echoed make -C sub, printed make[1]: Entering directory with the sub path, built in sub, then Leaving directory.

GNU Make companion · No. 05
recursion

Recursive make

$(MAKE) -C, and why

the sub-make, entered and leftall:  $(MAKE) -C submake[1]: Entering '.../sub'built in submake[1]: Leaving '.../sub'always use the variable$(MAKE), never theplain word make, forrecursive recipes, saysthe manual; it carriesthe flags down with it-C changes directory first; MAKEFLAGS rides along to the sub-make

For subdirectories, the recipe is $(MAKE) -C sub. Never the plain word make: the variable carries your path and flags to the child.

Our capture shows the child's own voice: make[1]: Entering directory, its output, then Leaving.

The [1] is a depth counter; the child is a full make with its own first goal and its own graph.

RUN IT THIS WEEK

Build a two-line sub makefile and count the [1] markers between Entering and Leaving directory.

Per the GNU Make manual, without -j make runs one recipe at a time, but the -j option tells make to execute many recipes simultaneously, and .NOTPARALLEL inhibits parallelism for some or all goals from within the makefile. This machine's captures: a slow and a quick recipe finished slow then quick serially; with -j2 the quick one finished first; .NOTPARALLEL under -j2 restored the slow-first order.

GNU Make companion · No. 06
parallel

Parallel builds

-j N, races, .NOTPARALLEL

many recipes at onceslow: sleep 0.4 thenquick: sleep 0.1 then$ make # serialslow done quick done$ make -j2 # parallelquick done slow done-j tells make toexecute many recipessimultaneously; jobswith no ordering canrace: shared filesare the danger.NOTPARALLEL holds one makefile serial even under -j

-j tells make to run many recipes simultaneously. Independent branches of the graph overlap; the finish order changes with the clock.

Our captures: serial gave slow then quick; -j2 flipped the finish order; .NOTPARALLEL pinned it serial again. Danger: recipes sharing one file can race.

Keep every recipe writing only its own outputs and -j becomes free speed.

RUN IT THIS WEEK

Give two independent recipes different sleeps and race them serially, with -j2, then with .NOTPARALLEL.

Per this book's plan, a small multi-file project walked end to end assembles the book's concepts: variables for the compiler and object list, one pattern rule with automatic variables, a clean goal marked phony. Every line in the diagram ran on this machine (GNU Make 4.3): the first make compiled both sources and linked, the second said 'app' is up to date, make clean removed the products, and a fourth make rebuilt everything.

GNU Make companion · No. 07
project

A real Makefile

Eleven lines, end to end

one small project, four real capturesCC = ccOBJS = main.o util.oapp: $(OBJS)%.o: %.c util.hclean:.PHONY: cleanall:, recipes use $@ $< $^cc -c main.c -o main.occ -c util.c -o util.occ -o app main.o util.oagain: 'app' is up to date.clean: rm -f main.o util.o appevery line captured hereeleven lines of makefile carry this book's every concept at once

Eleven lines: CC and OBJS, a link rule with $@ $^, one %.o: %.c pattern, clean marked .PHONY.

Our captures, all four: the build, then 'app' is up to date, then clean's rm, then a full rebuild after clean.

Every concept in this book is on this page at once. If you can read it cold, the book worked.

RUN IT THIS WEEK

Type those eleven lines for a two-file project of yours and run the four-make sequence yourself.

Index

Index


.PHONY6
A real Makefile10
A rule's anatomy4
Parallel builds9
patsubst and subst7
Recursive make8
The environment5