Most confusing Makefile bugs are really about how recipe lines are handed to the shell.
make passes each recipe line to a separate /bin/sh -c invocation. Shell state therefore doesn't carry over between lines: a cd dir on one line has no effect on the next, and neither does a shell variable assignment. Join dependent commands on one line with &&, or continue a line with a trailing backslash.
Line prefixes:
@don't echo the command before running it.-ignore a failure of this command and keep going.+run even undermake -n(used for recursive make calls).
Shell syntax and make syntax mix in recipes. make expands $(VAR) first, then the shell sees the result. So shell variables and command substitution need doubled dollars: $$f, $$(date).
Useful settings:
SHELL := /bin/bashand.SHELLFLAGS := -eu -o pipefail -cto use bash with strict mode..ONESHELL:runs the whole recipe in one shell, socdand variables persist (only the first line's@and-apply)..DELETE_ON_ERROR:deletes a target whose recipe failed. Without it, a half-written output file is newer than its inputs and the nextmakehappily treats it as up to date.
Recipes run sequentially within a rule and stop at the first non-zero exit status, unless the line has -.
Example
SHELL := /bin/bash
.SHELLFLAGS := -eu -o pipefail -c
.DELETE_ON_ERROR:
wrong:
cd /tmp
pwd # still the project directory!
right:
cd /tmp && pwd # /tmp
loop:
@for f in a b c; do \
echo "item $$f"; \
done # item a / item b / item c
tolerant:
-rm nonexistent-file # error printed, but make continues
@echo "stamp: $$(date +%F)"
report.txt: data.txt
grep -c ok data.txt | tee $@ # if grep fails, report.txt is deletedRemember
- Every recipe line runs in a new shell: use && or backslashes to join
- @ silences, - ignores errors; $$ passes a literal $ to the shell
- .DELETE_ON_ERROR removes half-written targets after a failure
Watch out for
- Splitting cd build and make on two lines and running the second command in the wrong directory.