Evaluation and Expansion¶
Page Maps¶
graph LR
family["Reproducible Research"]
program["Deep Dive Make"]
section["Build Graph Foundations Truth"]
page["Evaluation and Expansion"]
capstone["Capstone evidence"]
family --> program --> section --> page
page -.applies in.-> capstone
flowchart LR
orient["Orient on the page map"] --> read["Read the main claim and examples"]
read --> inspect["Inspect the related code, proof, or capstone surface"]
inspect --> verify["Run or review the verification path"]
verify --> apply["Apply the idea back to the module and capstone"]
Many Make bugs come from one quiet misunderstanding:
some things happen while Make is reading the file, and other things happen later when a recipe runs in the shell.
If you blur those moments together, builds feel nondeterministic even when the syntax is valid.
Two different moments¶
Read time¶
When Make parses the Makefile, it expands some variables, chooses rules, and builds its internal model of the graph.
Recipe time¶
Later, when a target needs rebuilding, Make launches a shell and executes the recipe.
That distinction matters because a value computed at read time can affect the graph before any recipe runs.
A simple timeline¶
flowchart LR
read["Make reads the file"] --> expand["Variables and functions expand"]
expand --> buildGraph["Rules and prerequisites become the working graph"]
buildGraph --> decide["Make decides what is out of date"]
decide --> shell["Recipes run in the shell for eligible targets"]
Keep that picture in your head. It explains many "Make is acting weird" moments.
The assignment operators you need first¶
:= immediate assignment¶
Make computes the value once when reading the file. This is a good default for lists you want to stay stable.
= recursive assignment¶
Make stores the recipe for computing the value and expands it later when the variable is used. This is useful, but it is easier to misuse.
?= and +=¶
Use ?= for defaults and += for simple extension. They are helpful, but they do not
replace the need to understand when evaluation happens.
A side-by-side example¶
The first line says, "decide this list now."
The second says, "decide this list whenever the variable is expanded later."
If the filesystem changes during the build or if the variable is used in several
different places, those two choices can lead to different behavior. That is why := is a
good default for graph-shaping values.
Why $(shell ...) deserves caution¶
$(shell ...) runs while Make is expanding the variable.
That means a line like this can change the graph before any recipe executes:
If that value is part of target naming, prerequisites, or command selection, your build definition itself changes every time Make reads the file.
That is how "we did not change the source" turns into "the build still changed."
A bad and better contrast¶
Bad:
Better:
The bad version changes every invocation for no semantic reason. The better version ties the graph to a declared mode that can be inspected, named, and discussed.
A safer Module 01 posture¶
- prefer
:=for computed lists and flags - sort discovered file lists so they stay stable
- treat
$(shell ...)as a design choice, not harmless convenience - inspect
make -pwhen variable origin or value seems surprising
What make -p is good for¶
Use make -p when you need to answer questions like:
- what value did this variable end up with
- where did that value come from
- which pattern rules exist after expansion
Do not treat it as a wall of text to fear. Treat it as a dump of the evaluated world.
Useful introspection tools¶
When a variable behaves oddly, these are worth knowing:
They tell you where a variable came from, how it expands, and what raw value it holds.
Those are not "advanced tricks." They are often the shortest path out of confusion.
Review prompts¶
- Which variables in this Makefile shape the graph itself?
- Which variables only affect recipe details?
- Would
:=make any important value more stable or more readable? - Is
$(shell ...)solving a real problem here, or hiding one?
Review questions¶
- Is this value supposed to be fixed when Make starts, or recomputed later?
- Could this expansion depend on time, environment, or filesystem order?
- If the value changes build meaning, where is that change made visible to the graph?
When you ask those questions early, Make stops feeling moody and starts feeling legible.