Recurring Build Antipatterns and Recovery¶
Page Maps¶
graph LR
family["Reproducible Research"]
program["Deep Dive Make"]
section["Migration Governance Tool Boundaries"]
page["Recurring Build Antipatterns and Recovery"]
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"]
This page collects the patterns that keep returning even after teams say they already know better.
The goal is not to mock bad Makefiles. The goal is to recognize familiar damage early enough to stop it from spreading.
Why antipatterns matter this late in the course¶
By Module 10, you already know a lot of individual facts:
- honest prerequisites matter
- parallel safety matters
- target meaning matters
- release boundaries matter
- observability matters
The problem in real repositories is not usually missing isolated facts. The problem is that the same bad combinations keep reappearing under new names.
That is why this page is about patterns, not trivia.
The sentence to keep¶
When you spot a suspicious build habit, ask:
what repeated form of truth loss or ownership drift am I looking at, and what is the smallest honest recovery path?
That question turns vague discomfort into action.
Antipattern 1: phony ordering instead of real edges¶
This is one of the oldest mistakes:
.PHONY: prepare compile
all: prepare compile
prepare:
@mkdir -p build
compile:
@cc -o build/app src/main.c
This looks harmless. The deeper problem is that compile does not actually say what it
needs.
Symptoms:
- a target works only because a phony step happened first
-jexposes missing directories or missing generated inputs- downstream targets rely on ritual order instead of real prerequisites
Recovery:
- make directory creation an order-only prerequisite where appropriate
- make generated inputs first-class graph nodes
- remove phony sequencing once real edges exist
Phony targets are not evil. Using them to hide real data or publication dependencies is.
Antipattern 2: multi-writer outputs hidden behind convenience¶
This appears in many forms:
- two targets both rewrite
build/app - a packaging target rebuilds product outputs
- a helper script refreshes generated files during unrelated commands
The team often describes this as "convenient" because it avoids repeating commands. What it really does is erase output ownership.
Symptoms:
- outputs change when running unrelated targets
- incremental behavior becomes hard to trust
- release incidents are hard to reproduce
Recovery:
- assign one writer to each trusted output
- split generation from packaging or publishing
- make convenience targets compose truthful targets instead of rewriting artifacts
Single-writer discipline is not a style preference. It is a survival rule.
Antipattern 3: recursive or opaque orchestration hiding the graph¶
Some inherited builds are not recursive in the strict GNU Make sense, but they still act like it:
- shell scripts call
makein several places - one target delegates to directory-local mini systems without visible edges
- a wrapper script chooses routes dynamically based on host state
Symptoms:
- nobody can describe the real graph from the top level
--tracehelps less than expected because the interesting work happens elsewhere- CI failures depend on where the wrapper script happened to branch
Recovery:
- keep the top-level contract small and explicit
- expose subsystem boundaries deliberately
- model shared outputs and inputs at the layer where they can be reviewed
- stop hiding major orchestration decisions in shell branching
The point is not "never call another tool." The point is "do not bury the ownership model."
Antipattern 4: stamps and manifests with no clear meaning¶
Stamps and manifests can be excellent tools. They can also become places teams dump uncertainty.
Warning signs:
- a stamp exists because "Make needed it somehow"
- the stamp is touched every run
- nobody can explain what semantic boundary it proves
- consumers depend on the stamp while the real output shape remains unclear
Example of a weak stamp:
What does the stamp mean here?
- that code generation ran
- that it succeeded
- that all outputs were validated
- that downstream consumers should trust every generated file
If the answer is "sort of all of those," the stamp is under-specified.
Recovery:
- define the boundary fact in one sentence
- make the stamp or manifest represent that one fact only
- keep consumers depending on real published outputs where direct edges are clearer
Antipattern 5: release or install routes that do too much¶
This pattern survives because it often "works" during demos:
The issue is not that each sub-step exists. The issue is that the target meaning is too broad to review.
Symptoms:
- one command mutates many different boundaries
- failures are described as generic "release issues"
- reruns are risky because side effects already happened
Recovery:
- separate validation, packaging, install, and deployment meanings
- keep publication boundaries inspectable
- make dangerous side effects opt-in and explicit
When a target means everything, it usually means nothing clearly.
Antipattern 6: performance fixes that erase truth¶
This one shows up late, often after the team has already suffered:
- skipping checks because they are slow
- caching outputs without modeling the cache boundary
- reducing rebuilds by ignoring semantic inputs
- removing trace or audit routes to reduce noise
Symptoms:
- the build is faster but less explainable
- stale outputs become harder to detect
- incidents take longer because evidence disappeared
Recovery:
- measure where the real cost lives first
- remove waste, not obligations
- add bounded observability routes instead of deleting evidence surfaces
- keep truth-preserving comparisons during optimization work
Speed is valuable. Truth is more valuable.
A small recovery rubric¶
When you detect an antipattern, classify the recovery path:
| Antipattern smell | First recovery move |
|---|---|
| ritual order dependence | expose real edge or order-only prerequisite |
| multi-writer output | assign one owner and remove hidden rewrites |
| opaque orchestration | surface the actual boundary and contract |
| meaningless stamp | define the represented fact before keeping the file |
| overgrown release target | split target meanings and side effects |
| truth-erasing optimization | restore evidence and semantic inputs first |
This keeps the fix proportional to the finding.
Do not confuse familiarity with legitimacy¶
Some patterns survive simply because teams have seen them for years:
- one giant
releasetarget - a helper script that "just knows" what to rebuild
- top-level targets that rewrite shared files as a convenience
- cleanup routes that also reset caches or developer state
Longevity does not make those healthy. It usually makes them expensive.
Module 10 asks you to say that clearly.
Failure signatures worth recognizing¶
"We have to run these targets in the right order or it breaks"¶
That is usually hidden-edge debt, not a usage quirk.
"Nobody wants to touch release because it does too much"¶
That is target-contract overload.
"The build got faster, but now weird stale states appear"¶
That is a truth-erasing performance repair.
"We keep adding stamps because the graph feels difficult"¶
That usually means the boundary facts were never clarified first.
What this page wants you to leave with¶
Strong maintainers can say more than "this feels brittle."
They can say:
this is a multi-writer output problem, or a hidden-edge problem, or a meaningless-stamp problem, and here is the smallest recovery move that restores honest ownership.
That is how antipattern recognition becomes useful instead of cynical.