summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md96
1 files changed, 95 insertions, 1 deletions
diff --git a/README.md b/README.md
index e7f1d4f..be5f643 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,7 @@ Everything is explained inline below. The handful of demos worth running end‑t
- [Prerequisites and rebuild semantics](#prerequisites-and-rebuild-semantics)
- [Special targets and directives](#special-targets-and-directives)
- [Running make](#running-make)
+- [Advanced topics](#advanced-topics)
- [Gotchas](#gotchas)
- [Visualising the dependency graph](#visualising-the-dependency-graph)
- [Worked examples](#worked-examples)
@@ -396,7 +397,7 @@ $(BATCHES):
mkdir -p $($@_DIR)
```
-(For generating whole *rules* dynamically rather than just lists, reach for `$(eval ...)`.)
+(For generating whole *rules* dynamically rather than just lists, reach for `$(eval ...)` — see [Advanced topics](#advanced-topics).)
### shell
@@ -626,6 +627,99 @@ all:
---
+## Advanced topics
+
+### Automatic prerequisite generation
+
+A `.o` file depends not just on its `.c` but on every header it `#include`s. Hand‑listing those headers is tedious and goes stale. GCC/Clang can emit the dependency list for you as a Makefile fragment, which you then `include`:
+
+```makefile
+CC := gcc
+CFLAGS := -Wall -O2
+DEPFLAGS := -MMD -MP
+
+SRCS := main.c util.c
+OBJS := $(SRCS:.c=.o)
+DEPS := $(OBJS:.o=.d)
+
+hello: $(OBJS)
+ $(CC) $(CFLAGS) $^ -o $@
+
+%.o: %.c
+ $(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@
+
+# Pull in the generated .d files. The leading - means "don't error if a
+# file is missing" — important on the first build, before any .d exists.
+-include $(DEPS)
+
+clean:
+ rm -f hello $(OBJS) $(DEPS)
+```
+
+- `-MMD` writes a `.d` file next to each `.o` listing the headers it included (use `-MD` if you also want system headers), *without* stopping compilation — plain `-M`/`-MM` print the dependency list *instead of* compiling.
+- `-MP` adds a dummy phony target for each header, so deleting a header doesn't cause a "No rule to make target" error on the next build.
+- `-include $(DEPS)` folds those rules back in, so editing a header now rebuilds exactly the objects that include it.
+
+### Searching other directories — VPATH and vpath
+
+By default Make only looks for prerequisites in the current directory. To keep sources in `src/` and headers in `include/` while building elsewhere, tell Make where to search:
+
+```makefile
+# VPATH variable: searched for ALL prerequisites (and targets).
+VPATH = src:include
+
+# vpath directive: pattern-scoped search (more precise).
+vpath %.c src
+vpath %.h include
+```
+
+When a prerequisite isn't in the current directory, Make searches these paths; if it finds `src/main.c`, then `$<` expands to `src/main.c` in the recipe. Note that VPATH only helps Make *find existing* files — it does not redirect where your recipe *writes* its output.
+
+### Generating rules with $(eval)
+
+`$(eval text)` parses `text` as if it appeared literally in the Makefile, so you can generate variables and whole rules programmatically. Paired with `define` and `foreach`, it stamps out a family of similar rules without hand‑writing each one:
+
+```makefile
+PROGRAMS := alpha beta gamma
+
+define PROGRAM_template
+$(1): $(1).o common.o
+ $$(CC) $$^ -o $$@
+endef
+
+$(foreach p,$(PROGRAMS),$(eval $(call PROGRAM_template,$(p))))
+```
+
+The escaping is the tricky part: inside the template, `$(1)` is the `$(call)` argument substituted *now*, while `$$` defers expansion to when the *generated* rule runs. So `$(call PROGRAM_template,alpha)` produces:
+
+```makefile
+alpha: alpha.o common.o
+ $(CC) $^ -o $@
+```
+
+### Recursive make
+
+In multi‑directory projects a top‑level Makefile can invoke Make in each subdirectory. Always use `$(MAKE)`, never a literal `make`, so flags and the parallel job server propagate downward:
+
+```makefile
+SUBDIRS := lib app
+
+.PHONY: all $(SUBDIRS)
+all: $(SUBDIRS)
+
+$(SUBDIRS):
+ $(MAKE) -C $@
+
+app: lib # build lib before app
+```
+
+- `$(MAKE)` carries `MAKEFLAGS` (including `-j`'s jobserver) into the sub‑make, so `make -j` parallelises across the whole tree.
+- `-C $@` runs Make in the subdirectory; the `Entering/Leaving directory` banners are printed automatically by `-C` and in sub‑makes (equivalent to `-w`) — silence them with `--no-print-directory`.
+
+Be aware that recursive make fragments the dependency graph — each sub‑make only sees its own piece, so cross‑directory dependencies can be missed (the classic critique is Peter Miller's *"Recursive Make Considered Harmful"*). A single non‑recursive Makefile using `include` + `$(eval)` avoids that, at the cost of more complexity.
+
+---
+
## Gotchas
**A target that never creates its file forces endless rebuilds.** Here `main.o` has no effective recipe (the condition is false, so the body is empty) and no file is ever produced, so Make treats it as perpetually out of date — and `hello`, which depends on it, relinks *every* run. In a large Makefile this is silent and maddening: