# GNU Make Notes, idioms, and worked examples for [GNU Make](https://www.gnu.org/software/make/) — the tool for describing how files are built from other files. Everything is explained inline below. The handful of demos worth running end‑to‑end live under [`examples/`](examples/). ## Table of Contents - [Introduction](#introduction) - [Rules](#rules) - [Recipes](#recipes) - [Variables](#variables) - [Automatic variables](#automatic-variables) - [Functions](#functions) - [Conditionals](#conditionals) - [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) --- ## Introduction GNU `make` defines a language for describing the relationships between files. Using `make` forces you to think about each component and how the pieces fit together. A Makefile is a list of **rules**. A rule has three parts — a **target**, its **prerequisites**, and a **recipe**: ``` target: prereq1 prereq2 command command ``` - The **target** is usually the file to create. - The **prerequisites** (also called dependencies) are files that must exist and be up to date before the target can be built. - The **recipe** is the shell command(s) that build the target from its prerequisites. If a prerequisite has its own rule, `make` builds it first. `make` only rebuilds a target when it is missing or older than one of its prerequisites — this is the whole point: it does the minimum work needed to bring things up to date. > **The tab rule.** Every recipe line *must* start with a real tab character, not spaces. This trips up everyone at least once. If you must use something else, set [`.RECIPEPREFIX`](https://www.gnu.org/software/make/manual/html_node/Special-Variables.html) (e.g. `.RECIPEPREFIX = >`). The examples below assume a small sample dataset: ```bash mkdir data; for i in {1..4}; do echo ${i} > data/${i}.fa; done ``` --- ## Rules A rule defines a target and its prerequisites (if any). There are several kinds: 1. **Explicit rules** name a specific target — the most common kind. 2. **Pattern rules** use the `%` wildcard instead of explicit filenames, so the rule applies to any target matching the pattern. 3. **Implicit rules** are pattern or suffix rules built into Make's database (e.g. how to make `%.o` from `%.c`). 4. **Static pattern rules** are pattern rules restricted to a specific list of targets: `targets: pattern: prereq-pattern`. *Suffix rules* also exist but are obsolete — pattern rules are clearer and more general. ### Explicit and pattern rules A small C build shows both an explicit rule and a pattern rule working together: ```makefile CC := gcc CFLAGS := -Wall -O2 # Pattern rule: any %.o can be built from the matching %.c. # -c compiles without linking. %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ # Explicit rule: link the objects into the final binary. hello: main.o util.o $(CC) $(CFLAGS) $^ -o $@ ``` `make hello` builds `main.o` and `util.o` via the pattern rule, then links them. (`$<`, `$@` and `$^` are *automatic variables* — see [below](#automatic-variables).) ### The default goal Running `make` with no target builds the **default goal**: the first *normal* target in the file. Make skips targets whose names start with a dot (special targets like `.PHONY`) and pattern rules when choosing it, so neither becomes the default. By convention the default goal is a phony `all` target that depends on everything you normally want built: ```makefile all: hello ``` You can set it explicitly with `.DEFAULT_GOAL := some_target`. ### Multiple targets and grouped targets Listing several targets in one rule means the recipe runs **once per target**, with `$@` set to whichever is being built: ```makefile # `make first.txt` makes first.txt; `make second.txt` makes second.txt. first.txt second.txt: @echo "Generating: $@" touch $@ ``` When a single recipe produces *all* the listed files at once, use a **grouped target** with `&:` (GNU Make 4.3+). The recipe runs only once for the whole group: ```makefile # Both files are produced together by one invocation. fifth.txt sixth.txt &: @echo "Generating fifth.txt and sixth.txt together" touch fifth.txt sixth.txt ``` --- ## Recipes A **recipe** is the set of shell commands Make runs to update a target. Each line is indented with a tab. ### Each line runs in its own shell By default Make runs every recipe line in a *separate* shell, so `cd`, shell variables, and other state do **not** carry from one line to the next: ```makefile broken: cd /tmp pwd # prints the original directory, NOT /tmp working: cd /tmp && pwd # prints /tmp ``` Use a trailing backslash to continue one command across lines in a single shell: ```makefile also_working: cd /tmp && \ pwd ``` ### .ONESHELL `.ONESHELL:` (GNU Make 3.82+) runs an entire recipe in one shell, so multi‑line recipes share state naturally: ```makefile .ONESHELL: my_target: cd /tmp pwd # now this prints /tmp ``` Note that `.ONESHELL` is **global** — it affects every recipe in the Makefile, so don't mix it with demos that rely on separate‑shell behaviour. ### Line prefixes | Prefix | Effect | |--------|--------| | `@` | Suppress echoing of the command (Make normally prints each command before running it) | | `-` | Ignore a non‑zero exit status from the command | | `+` | Run the command even under `make -n` (dry run) | They combine, e.g. `-@cmd` silences output *and* ignores errors: ```makefile errors: @echo "--- ignoring a failing command ---" -false @echo "Make continued past 'false' because of the - prefix" ``` ### Escaping `$` Make expands `$(VAR)` and `${VAR}` **before** the shell sees the line. To pass a literal `$` to the shell (for shell variables, `awk`, etc.) escape it as `$$`: ```makefile demo: @echo "Make variable: $(MY_VAR)" @SHELL_VAR="hello"; echo "Shell variable: $$SHELL_VAR" ``` ### Canned recipes (`define` / `endef`) A **canned recipe** is a reusable command sequence stored in a variable. Define it once, expand it with `$(name)` wherever it's needed. Automatic variables like `$@` inside it resolve to the *calling* rule's target: ```makefile define log_start @echo "=== Starting: $@ ===" @date endef define log_end @date @echo "=== Finished: $@ ===" endef target_a: $(log_start) @echo "Building target_a" $(log_end) target_b: $(log_start) @echo "Building target_b" $(log_end) ``` See the manual on [recipes](https://www.gnu.org/software/make/manual/html_node/Recipes.html) and [canned recipes](https://www.gnu.org/software/make/manual/html_node/Canned-Recipes.html). --- ## Variables ### Assignment operators | Operator | Name | When the right‑hand side is expanded | |----------|------|--------------------------------------| | `=` | recursive (lazy) | Every time the variable is **used** (late binding) | | `:=` (and POSIX `::=`) | simple (immediate) | Once, **at the point of definition** (early binding) | | `?=` | conditional | Like `=`, but the assignment happens **only if the variable is not already set** | | `+=` | append | Appends to the current value (or sets it, if previously unset) | | `:::=` | immediate‑with‑escape (4.4+) | Expanded immediately, then results re‑escaped; rarely needed | > **Common confusion:** `:=` is the *immediate* operator (value fixed when defined); `?=` is the *set‑only‑if‑undefined* operator. They are not interchangeable. ```makefile A = $(B) # recursive: A re-expands $(B) every time A is used B := hello # simple: evaluated once, here C ?= default # conditional: only assigned if C has no value yet D += more # append to D ``` ### Late vs early binding The difference between `=` and `:=` is *when* references inside the value are resolved: ```makefile BAR := initial SIMP := $(BAR) # := captures "initial" right now REC = $(BAR) # = just stores the reference $(BAR) BAR := changed # reassign BAR # When expanded later: # SIMP -> "initial" (snapshot taken at definition) # REC -> "changed" (resolved at use, sees the new BAR) ``` This matters most with `$(shell ...)`: a recursive variable re‑runs the command on every expansion, a simple one runs it once: ```makefile NOW_EQ = $(shell date +%s) # re-runs `date` every time NOW_EQ is used NOW_SIMPLE := $(shell date +%s) # runs `date` once, at parse time ``` ### Override precedence When the same variable is set in more than one place, the priority is: ``` command line > Makefile > environment > built-in default ``` This is the *default* ordering. `make -e` (`--environment-overrides`) flips the middle two, making environment variables win over Makefile assignments. `?=` is convenient for *overridable defaults*, because it backs off if any higher‑priority source already set the value: ```makefile CC ?= gcc # `make CC=clang` wins; otherwise CC is gcc ``` To make a Makefile assignment win even over the command line, use `override`: ```makefile override USER := Mr. President # `make USER=...` cannot change this ``` ### Variable scope A variable is global by default, but can be scoped to a target or a pattern: ```makefile VAR = global scope # Target-specific: VAR has this value only while building scoping_local # (and its prerequisites). scoping_local: VAR = local scope scoping_local: @echo "In $@, VAR is $(VAR)" # Pattern-specific: applies to any target matching the pattern p% p%: VAR = set with wildcard ``` ### Environment variables Make imports the environment as variables. A variable that isn't set expands to the empty string: ```makefile echo_env: @echo "Hello $(USER), your home is $(HOME)" @echo "This env var does not exist: [$(DOESNOTEXIST)]" @echo "Exported var PACIFIC=[$(PACIFIC)]" # run `export PACIFIC=ocean` first ``` --- ## Automatic variables Inside a recipe, Make sets these to describe the rule being built. They are the key to writing generic rules: | Variable | Meaning | |----------|---------| | `$@` | The target name | | `$<` | The first prerequisite | | `$^` | All prerequisites, duplicates removed | | `$+` | All prerequisites, duplicates kept, in order | | `$?` | Prerequisites that are newer than the target | | `$*` | The stem matched by `%` in a pattern rule | | `$(@D)` / `$(@F)` | The directory / file parts of `$@` | ```makefile target: one two three four five @echo "$$@ target name: $@" @echo "$$< first prereq: $<" @echo "$$^ all prereqs: $^" @echo "$$? prereqs newer than target: $?" @echo "second prereq via word: $(word 2,$^)" @echo "all but the first prereq: $(filter-out $<,$^)" ``` Note that `$(filter-out $<,$^)` removes *every* prerequisite equal to `$<` (a value match, not a positional one); because `$^` de‑duplicates, that happens to mean "all but the first" here. `$(@D)` is handy when a tool needs the *directory* containing a file rather than the file itself. --- ## Functions Make has built‑in functions for text processing; they expand to strings before recipes run. ### Text and list functions Assuming `BAMS := $(wildcard *.bam)`: | Function | Purpose | Example | |----------|---------|---------| | `$(wildcard pat)` | Files matching a glob | `$(wildcard *.bam)` | | `$(patsubst pat,repl,text)` | Pattern substitution over words | `$(patsubst %,%.bai,$(BAMS))` | | `$(subst from,to,text)` | Plain (non‑pattern) string substitution | `$(subst -,_,$(BAMS))` | | `$(addprefix p,names)` | Prepend a prefix to each word | `$(addprefix raw-,$(BAMS))` | | `$(addsuffix s,names)` | Append a suffix to each word | `$(addsuffix .bai,$(BAMS))` | | `$(filter pat,text)` | Keep words matching the pattern(s) | `$(filter sample1%,$(BAMS))` | | `$(filter-out pat,text)` | Drop words matching the pattern(s) | `$(filter-out sample1%,$(BAMS))` | | `$(sort list)` | Sort words and remove duplicates | `$(sort $(BAMS))` | | `$(notdir names)` | Strip the directory part | `$(notdir path/to/x.bam)` → `x.bam` | | `$(dir names)` | Keep only the directory part | `$(dir path/to/x.bam)` → `path/to/` | | `$(basename names)` | Strip the suffix | `$(basename x.bam)` → `x` | | `$(suffix names)` | Keep only the suffix | `$(suffix x.bam)` → `.bam` | A common idiom — derive one file list from another: ```makefile fastas := $(wildcard data/*.fa) # data/1.fa data/2.fa ... -> result/1.bam result/2.bam ... bams := $(patsubst %,result/%.bam,$(basename $(notdir $(fastas)))) ``` You can also use a **substitution reference**, a shorthand for the common `patsubst` case `$(VAR:pattern=replacement)`: ```makefile BOOST_LIBRARIES := system filesystem regex # -> -lboost_system -lboost_filesystem -lboost_regex LDFLAGS += $(BOOST_LIBRARIES:%=-lboost_%) ``` ### foreach `$(foreach var,list,text)` expands `text` once per word in `list`: ```makefile # Wrap each BAM in brackets: [a.bam] [b.bam] ... BAMS_BRACKETS := $(foreach f,$(BAMS),[$(f)]) ``` Combined with **computed variable names** (`$($(x)_SUFFIX)`), `foreach` can drive a small data model: ```makefile BATCHES = batch1 batch2 batch1_ID = ID1 batch2_ID = ID2 batch1_DIR = dir_batch1 batch2_DIR = dir_batch2 # Expands to: dir_batch1 dir_batch2 all: $(foreach batch,$(BATCHES),$($(batch)_DIR)) $(BATCHES): @echo "Processing $@ using ID $($@_ID) into $($@_DIR)" mkdir -p $($@_DIR) ``` (For generating whole *rules* dynamically rather than just lists, reach for `$(eval ...)` — see [Advanced topics](#advanced-topics).) ### shell `$(shell cmd)` runs a command and captures its output: embedded newlines become spaces, and the trailing newline is stripped: ```makefile DATE := $(shell date +%F) ``` Remember the timing rule from [Variables](#variables): with `:=` the command runs once at parse time; with `=` it re‑runs on every expansion. ### Diagnostics: info, warning, error These print during parsing (not in a recipe). `$(error ...)` aborts the build: ```makefile $(info CFLAGS is $(CFLAGS)) # informational message $(warning Debugging enabled) # warning, build continues $(error Cannot continue) # print the message and abort parsing ``` See [Conditionals](#conditionals) for the common `ifndef`/`$(error)` pattern used to guard against unset variables. ### User‑defined functions Define a function as a variable; call it with `$(call name,arg1,arg2,...)`, where `$1`, `$2`, … are the arguments: ```makefile make_date = $1-$2-$3 today := $(call make_date,2022,09,15) # -> 2022-09-15 # Abort the build if a required command is missing. assert-command-present = $(if $(shell command -v $1),,$(error '$1' is missing)) $(call assert-command-present,samtools) ``` --- ## Conditionals `ifeq` / `ifneq` compare two expanded values. `ifdef` / `ifndef` test whether a variable is **defined to a non‑empty value, without expanding it first** — so `ifdef foo` is true for `foo = $(bar)` even when `$(bar)` itself expands to empty. To test the *expanded* value for emptiness, use `ifeq ($(VAR),)` (shown below). Conditionals are evaluated when the Makefile is **parsed**, not when a recipe runs. ```makefile my_target: ifeq ($(COND),1) echo Condition 1 else ifeq ($(COND),2) echo Condition 2 else echo Condition else endif ``` A frequent pattern is the **emptiness test** — note the empty first argument: ```makefile # True when BOOST_ROOT is set to something non-empty. ifneq (,$(BOOST_ROOT)) BOOST_INCLUDEDIR ?= $(BOOST_ROOT)/include endif ``` And a guard that aborts early if a required variable is unset: ```makefile ifndef PACIFIC $(error Variable PACIFIC is not set) endif ``` ### Requiring a minimum Make version Several features in these notes need a recent Make (`.ONESHELL` 3.82+, grouped `&:` targets 4.3+, `:::=` 4.4+). Guard for them at parse time using the built‑in `$(MAKE_VERSION)`: ```makefile need := 4.3 ok := $(filter $(need),$(firstword $(sort $(MAKE_VERSION) $(need)))) ifeq (,$(ok)) $(error GNU Make $(need)+ required, but this is $(MAKE_VERSION)) endif ``` `$(sort ...)` orders the running version against the one you need, `$(firstword ...)` takes the lower of the two, and `$(filter ...)` is non‑empty only when the running version is at least `need`. --- ## Prerequisites and rebuild semantics Make decides whether to rebuild a target by comparing modification times (mtimes): if any **normal** prerequisite is newer than the target — or the target is missing — the recipe runs. ### Order‑only prerequisites Sometimes a target needs another to *exist* but should not rebuild just because that other thing changed — the classic case is an output directory. List such prerequisites after a pipe `|`; their timestamps are ignored: ```makefile # build/output.txt depends on input.txt (normal) and the build/ dir (order-only). # A newer build/ timestamp will NOT trigger a rebuild of output.txt. build/output.txt: input.txt | build cp $< $@ build: mkdir -p $@ ``` > Beware **symlink** targets: Make checks the mtime of the file the link *resolves to*, not the link itself — which has surprising consequences. See [Gotchas](#gotchas). --- ## Special targets and directives ### .PHONY By default a target is a *file* target. A **phony** target is one that doesn't correspond to a file — `all`, `clean`, `install`, `test`, and friends. Declaring it phony does two things: it stops Make being confused by a real file of the same name (e.g. a file literally called `clean`), and it makes the target *always* run: ```makefile .PHONY: clean clean: rm -rf *.o ``` A phony target is, in effect, always out of date, so `make clean` runs regardless of the filesystem. You can also use a phony name as a short alias for a long real target (see [Worked examples](#worked-examples)). ### .DELETE_ON_ERROR If a recipe exits non‑zero partway through, the half‑written target file is left behind and looks up to date. `.DELETE_ON_ERROR:` tells Make to delete a target whose recipe fails: ```makefile .DELETE_ON_ERROR: output1.txt: touch $@ # Write a partial result, then fail. output2.txt: output1.txt touch $@ && false ``` When the recipe exits non‑zero, the half‑written `output2.txt` is removed instead of being left behind looking up to date: ```console $ make touch output1.txt touch output2.txt && false make: *** [Makefile:8: output2.txt] Error 1 make: *** Deleting file 'output2.txt' ``` Make *also* deletes a target if you interrupt a running recipe with Ctrl‑C, but that cleanup happens regardless of `.DELETE_ON_ERROR` — this special target is specifically about non‑zero *exit status*. Related special targets worth knowing: `.SECONDARY` and `.PRECIOUS` (keep intermediate files), `.INTERMEDIATE`, and `.NOTPARALLEL` (force serial builds). ### SHELL Set the shell used for recipes (recipes default to `/bin/sh`). Use this when you rely on bash features: ```makefile SHELL := /bin/bash ``` ### include `include` suspends reading the current Makefile to read another first — useful for pulling configuration out into its own file: ```makefile include config.mk ``` This is also how you pin tool versions: keep them in a small included file (e.g. `tool_versions` with `samtools_version=1.17`) and reference `$(samtools_version)` in your rules. The [dragmap example](examples/dragmap/) uses `include config.mk` to assemble compiler flags conditionally. --- ## Running make `make` accepts many flags; the full list is in `man make` or `make --help`. The most useful: | Flag | Long form | What it does | When it's useful | | --- | --- | --- | --- | | `-j [N]` | `--jobs[=N]` | Run up to `N` recipes in parallel (no `N` = as many as possible). | Anything CPU/IO‑bound with independent targets — the single biggest speed‑up. Prerequisites must be declared correctly or parallel builds race. | | `-O[type]` | `--output-sync[=type]` | Group each recipe's output so parallel jobs don't interleave. `type` ∈ `none`, `line`, `target` (default), `recurse`. | Whenever you use `-j`. Without it, parallel logs are unreadable. | | `-k` | `--keep-going` | Keep building unrelated targets after a failure instead of stopping. | Long batch runs where you want to see *all* failures in one pass. | | `-n` | `--dry-run` | Print the commands Make *would* run without executing them. | Sanity‑checking a Makefile, or generating a command list (e.g. for `make2graph`). | | `-B` | `--always-make` | Treat every target as out of date and rebuild unconditionally. | Forcing a clean rebuild, or when you changed something Make can't detect. | | `-d` | `--debug` | Verbose info on which rules fire, which prerequisites are considered, and why targets are (or aren't) rebuilt. | Figuring out *why* Make is rebuilding something — or refusing to. Pair with `-n`. | | `-C dir` | `--directory=dir` | `cd` into `dir` before reading the Makefile. | Running a sub‑project's Makefile from elsewhere. | | `-f file` | `--file=file` | Use `file` instead of the default (`Makefile`/`makefile`/`GNUmakefile`). | Keeping multiple Makefiles in one directory. | | `-s` | `--silent` | Suppress command echoing (as if every line were prefixed with `@`). | Cleaner output when you only care about program messages. | | `-i` | `--ignore-errors` | Ignore non‑zero exit codes from recipes. | Rarely a good idea; occasionally useful for cleanup targets. | | `-p` | `--print-data-base` | Print Make's internal database (variables, rules, implicit rules) after parsing. | Discovering implicit rules and variables in scope; pair with `-n`. | A useful combination for real workloads: ```bash make -j4 -O -k all 2>&1 | tee results/run.log ``` This runs up to four recipes in parallel, keeps the interleaved output readable, doesn't stop on the first failure, and captures stdout+stderr to a log. Notes on `-j` and `-O`: - `-O target` (the default) prints each recipe's output as one block when it finishes — the most readable mode. - `-O line` synchronises at line granularity, so you see progress in real time without garbled lines. - `-O none` is the legacy interleaved behaviour — almost never what you want with `-j`. - `-k` with `-j` skips failed targets and their dependents but keeps other branches going; Make still exits non‑zero so CI catches the failure. - `-n` suppresses *recipe* execution only — parse‑time `$(shell ...)` functions still run. ### Passing variables on the command line Variables given on the command line override the Makefile (unless `override` is used). This is the standard way to toggle behaviour: `make CC=clang`, `make DEBUG=1`, `make VERBOSE=1`. ### A verbosity toggle A tidy idiom for switchable command echoing — quiet by default, full commands when asked: ```makefile VERBOSE ?= 0 ifeq ($(VERBOSE),0) Q := @ else Q := endif all: $(Q)printf "Hello, World!\n" ``` `make` is quiet; `make VERBOSE=1` echoes the command. The same `?=`/conditional pattern drives a debug toggle — e.g. `DEBUG ?= 0` and, when set, `CFLAGS += -g` plus a `$(warning Debugging enabled)`. --- ## 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: ```makefile hello: main.o touch $@ main.o: ifeq (false,true) echo $@ # never runs -> main.o is never created -> hello always rebuilds endif ``` **Symlink targets are judged by the linked file's mtime.** When a target is a symlink, Make uses the mtime of the file it *resolves to*, not the link itself. So recreating or touching the link without changing the underlying file won't trigger dependents, and a prerequisite that is newer than the underlying file keeps the step re‑running until that file is regenerated. **`$` needs escaping in recipes.** Make consumes a single `$` for its own expansion. Use `$$` for a literal `$` (shell variables, `awk` programs, etc.). See [Recipes](#recipes). **Don't depend on a list of unknown targets.** It's tempting to generate files whose names you don't know ahead of time and have Make skip them once they exist. This is fragile: once `$(FILES)` exists, Make won't rebuild those files even when their inputs change, and you can't tell whether you got the 5 files you expected or only 2. Prefer knowing your targets up front. **Parallel builds expose under‑declared prerequisites.** Under `-j`, two recipes that secretly share a file (without declaring it) can race. If a parallel build is flaky but a serial one isn't, look for a missing prerequisite. --- ## Visualising the dependency graph [`make2graph`](https://github.com/lindenb/makefile2graph) turns Make's dry‑run output into a Graphviz diagram — a quick way to see how targets connect. ```console # Build make2graph (one-off) git clone https://github.com/lindenb/makefile2graph.git cd makefile2graph && make # Install graphviz for `dot` sudo apt update && sudo apt install -y graphviz # Render the graph: -B force all targets, -n dry-run, -d debug output make -Bnd | ./make2graph | dot -Tpng -o out.png ``` A runnable demo and a sample `out.png` are in [`examples/visual/`](examples/visual/). To explore Make's *internal* knowledge (implicit rules, variables) instead of just your own targets, use `make -p`. > `-n` only suppresses *recipe* execution — parse‑time `$(shell ...)` calls still run, so `make -Bnd` is not always fully side‑effect‑free when generating the graph. --- ## Worked examples Short patterns are inlined here; the longer, runnable ones live under [`examples/`](examples/). ### Aliasing long targets Give long real filenames short, memorable phony aliases. The trick: targets and prerequisites depend on the *real* files (via variables); the aliases just point at them. ```makefile .PHONY: all first one := file_one_one_one_one_one.txt all: $(one) first: $(one) # `make first` is the convenient alias... $(one): # ...but the real work hangs off the file target touch $@ ``` ### Versioned symlink backups Keep one stable symlink that always points at the latest version, while retaining a timestamped backup of every version: ```makefile my_symlink: dep.txt DS=$$(date +%Y%m%d_%H%M%S) && \ cat $< > $$DS && \ ln -fs $$DS $@ ``` Editing `dep.txt` makes the prerequisite newer than the target, so the rule re‑runs — producing a new timestamped file and repointing the link. (The symlink‑mtime caveat in [Gotchas](#gotchas) applies if you instead try to trigger rebuilds via the link itself.) ### Data‑driven targets Build a target list partly from a file, mixing static and discovered names: ```makefile FILES := 1.txt 2.txt 3.txt MORE_FILES := $(shell cut -f1 -d, files_to_create.csv) all: $(FILES) $(MORE_FILES) final.txt %.txt: touch $@ final.txt: $(FILES) $(MORE_FILES) touch $@ ``` (See the unknown‑targets caveat in [Gotchas](#gotchas) before relying on this.) ### Debugging a build's environment — [`examples/dragmap/`](examples/dragmap/) A real case study: building [DRAGMAP](https://github.com/Illumina/DRAGMAP) against an alternate Boost install. It uses `include`, `$(info ...)` to dump variables, and `ifneq` emptiness tests to assemble `CPPFLAGS`/`LDFLAGS` from `BOOST_ROOT` / `BOOST_INCLUDEDIR` / `BOOST_LIBRARYDIR`. The `run.sh`/`test*.sh` scripts toggle the environment variables to show which combination fixes the link errors. ### Orchestrating SGE cluster jobs — [`examples/sge/`](examples/sge/) Using Make as a dependency‑aware front end for a job scheduler: each target submits a job with `qsub` and chains them with `-hold_jid` so a job waits for its predecessor, while independent targets run in parallel. `submit.sh` runs Make itself as a parallel job (`make -j $NSLOTS`). SGE is largely legacy now; the same pattern applies to Slurm (`sbatch --dependency=afterok:`).