diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 732 |
1 files changed, 732 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..e7f1d4f --- /dev/null +++ b/README.md @@ -0,0 +1,732 @@ +# 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) +- [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 +<tab>command +<tab>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 ...)`.) + +### 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)`. + +--- + +## 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:<id>`). |
