diff options
| author | Dave Tang <davetingpongtang@gmail.com> | 2026-06-23 08:35:14 +0900 |
|---|---|---|
| committer | Dave Tang <davetingpongtang@gmail.com> | 2026-06-23 08:35:14 +0900 |
| commit | 5b41ec0d671ca760b1d03d5b35585f8670f5507a (patch) | |
| tree | 041f7147ff743a632d005404ab399f15101623b3 /README.md | |
| parent | 319f10b1f20170adf7efa05938b6bb23584c4729 (diff) | |
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 96 |
1 files changed, 95 insertions, 1 deletions
@@ -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: |
