Module 07: Build Architecture, Layered Includes, and Build APIs¶
Module Position¶
flowchart TD
family["Reproducible Research"] --> program["Deep Dive Make"]
program --> module["Module 07: Build Architecture, Layered Includes, and Build APIs"]
module --> lessons["Lesson pages and worked examples"]
module --> checkpoints["Exercises and closing criteria"]
module --> capstone["Related capstone evidence"]
flowchart TD
purpose["Start with the module purpose and main questions"] --> lesson_map["Use the lesson map to choose reading order"]
lesson_map --> study["Read the lessons and examples with one review question in mind"]
study --> proof["Test the idea with exercises and capstone checkpoints"]
proof --> close["Move on only when the closing criteria feel concrete"]
Read the first diagram as a placement map: this page sits between the course promise, the lesson pages listed below, and the capstone surfaces that pressure-test the module. Read the second diagram as the study route for this page, so the diagrams point you toward the Lesson map, Exercises, and Closing criteria instead of acting like decoration.
Once a build works, teams immediately try to reuse it. This is where Makefiles often decay into include tangles, hidden overrides, and pseudo-frameworks that are impossible to audit. Module 07 is about reuse that preserves truth instead of diluting it.
The point is not to make Make clever. The point is to make larger builds explainable.
Capstone exists here as corroboration. The local layering exercises should make the architecture legible before you compare them to the reference build layout.
Before You Begin¶
This module works best after Modules 02-06, when you already trust the graph and now need to scale the build without turning it into a private language.
Use this module if you need to learn how to:
- define a stable public target surface for other humans and tools
- split
mk/*.mkfiles by responsibility instead of habit - reuse rule shapes without obscuring graph structure
At a glance¶
| Focus | Learner question | Capstone timing |
|---|---|---|
| public targets | "Which entrypoints can humans and CI rely on?" | use capstone help after you define your own API |
| include layering | "Which file is allowed to set policy?" | inspect the reference layout once the local split feels clear |
| reusable macros | "How do I reduce duplication without hiding behavior?" | compare your local choices to capstone/mk/*.mk |
Proof loop for this module:
Capstone corroboration:
- inspect target boundaries in
capstone/Makefile - inspect layer separation in
capstone/mk/*.mk - use
make PROGRAM=reproducible-research/deep-dive-make program-help
The module is doing its job only if you can point to a layer boundary and explain why it exists without appealing to habit.
1) Table of Contents¶
- Table of Contents
- Learning Outcomes
- How to Use This Module
- Core 1 — Public Targets as a Stable Build API
- Core 2 — Layered
mk/Includes Without Hidden Mutation - Core 3 — Macros,
call, and Reuse That Stays Auditable - Core 4 — Discovery, Namespacing, and Repository Growth
- Core 5 — Reviewing Build Architecture Before It Rots
- Capstone Sidebar
- Exercises
- Closing Criteria
2) Learning Outcomes¶
By the end of this module, you can:
- define a stable public target surface for a larger Make-based system
- split build logic across
mk/*.mklayers without creating graph mutation surprises - use macros for repeated rule shapes while keeping expansion inspectable
- scale source discovery and naming conventions without recursive-make drift
- review a build architecture for API clarity, override safety, and future maintenance
3) How to Use This Module¶
Take one medium-sized local project and split it into:
Then answer three questions in code:
- Which targets are public?
- Which include layers are allowed to set policy?
- Which macros reduce duplication without hiding the graph?
If you cannot answer those by inspection, the architecture is already too clever.
4) Core 1 — Public Targets as a Stable Build API¶
A serious Make system should have a small public surface:
alltestselftestclean- any explicitly documented release or audit targets
Everything else is implementation detail.
The moment teams start calling internal helper targets from CI or release scripts, the build stops having an API and starts having archaeology.
5) Core 2 — Layered mk/ Includes Without Hidden Mutation¶
Use include layers for separation of concerns, not for surprise behavior.
A safe layering pattern looks like this:
common.mkdefines tools, flags, and shared shell disciplineobjects.mkdiscovers sources and maps outputs deterministicallytargets.mkdefines the real build graphrelease.mkadds optional packaging or publication surfaces
Each layer should have one job. Includes should add declared structure, not mutate earlier
lists in ways only make -p can reveal.
6) Core 3 — Macros, call, and Reuse That Stays Auditable¶
Macros are justified when they enforce invariants or reduce repeated, truthful rule shapes. They are dangerous when they create graph edges nobody can read.
Healthy macro use:
- one macro for atomic publication
- one macro for a repeated compile or package pattern
- bounded use of
callwith explicit arguments - quarantined
eval, only when the generated structure remains inspectable and optional
The review standard is simple: another engineer should still be able to explain the graph
with make -p and the final expanded rules.
7) Core 4 — Discovery, Namespacing, and Repository Growth¶
As repositories grow, two things break first:
- unstable discovery order
- target-name collisions
Architecture rules that help:
- root all discovery from known directories
- sort discovered lists before they affect graph structure
- namespace generated outputs by directory or component
- keep one top-level DAG even when the repository has many subsystems
“Recursive make” is not evil because it uses recursion. It is dangerous when it hides the real graph behind disconnected local truths.
8) Core 5 — Reviewing Build Architecture Before It Rots¶
Review questions for larger Make systems:
- Which targets are public, and are they documented?
- Which files define policy, and which define graph shape?
- Which overrides are safe, and which would change semantics invisibly?
- Which macros enforce a rule, and which merely compress syntax?
- Could a newcomer locate the path from source discovery to final artifact publication?
If the answer to the last question is no, the build has become a private language.
9) Capstone Sidebar¶
Use the capstone as the reference architecture:
Makefiledefines the public target surfacemk/common.mk,mk/objects.mk,mk/stamps.mk, and related files separate concerns- repros remain outside the main build API
- helper macros reinforce correctness rather than obscure it
10) Exercises¶
- Split one medium Makefile into
mk/*.mklayers and document the public targets. - Replace one copy-pasted rule family with a macro that still leaves the graph auditable.
- Introduce deterministic discovery for a growing source tree and prove stable ordering.
- Review one legacy Make architecture and write down the private assumptions it currently hides.
11) Closing Criteria¶
You pass this module only if you can demonstrate:
- a documented public target surface
- a layered include structure with clear ownership
- at least one reusable macro that enforces correctness instead of hiding truth
- deterministic discovery and naming that survives repository growth
Directory glossary¶
Use Glossary when you want the recurring language in this module kept stable while you move between lessons, exercises, and capstone checkpoints.