Debugging Makefiles

Make · Expert · 12 min

Make's terse errors are easy once you know how to ask it what it's thinking.

When a build does something unexpected, ask make directly:

  • make -n prints the commands it would run, without running them.
  • make --trace (4.0+) prints each recipe with the reason it ran, for example "target 'build/x.o' does not exist" or "prerequisite 'src/x.c' is newer than target".
  • make --debug=b (basic) or --debug=v (verbose) explains its decisions; --debug=i adds implicit-rule search, the answer to "why was that weird built-in rule used?".
  • make -p dumps the whole database after reading: every variable with its origin, and every rule. Combine with -q or a fake target to avoid building: make -pq | less.
  • $(info VAR=$(VAR)), $(warning ...) print during the read phase; $(error ...) stops.
  • make -B -n target shows the complete recipe chain for a target.

Common messages decoded:

  • missing separator: a recipe line not indented with a tab, or a stray line of text.
  • No rule to make target 'X', needed by 'Y': a prerequisite doesn't exist and no rule (explicit or pattern) can create it; often a typo, a deleted header still in a .d file, or a pattern rule whose source path doesn't match.
  • Circular X <- Y dependency dropped: the graph has a cycle.
  • target 'X' given more than once in the same rule or overriding recipe: two recipes for one target, usually a copy-paste mistake.

$(origin VAR) and $(flavor VAR) tell you where a variable came from and whether it's recursive or simple.

Example

# Makefile with a mistake: header path is wrong
build/app.o: src/app.c src/app.hh
	cc -c $< -o $@

$(info CC is '$(CC)' from $(origin CC), flavor $(flavor CC))

# $ make build/app.o
# CC is 'cc' from default, flavor recursive
# make: *** No rule to make target 'src/app.hh', needed by 'build/app.o'.  Stop.

# after fixing it:
# $ touch src/app.c && make --trace build/app.o
# Makefile:3: update target 'build/app.o' due to: src/app.c
# cc -c src/app.c -o build/app.o

# $ make -p -f /dev/null | grep -A2 '^%.o: %.c'
# %.o: %.c
# #  recipe to execute (built-in):
# 	$(COMPILE.c) $(OUTPUT_OPTION) $<

Remember

  • --trace says why each recipe ran; -n shows what would run
  • -p dumps all variables and rules, including built-ins
  • 'No rule to make target' means a missing file with no rule to create it

Watch out for

  • Guessing at rebuild problems instead of running make --trace or --debug=b, which states the reason.

Check yourself

  1. make reports: No rule to make target 'util.h', needed by 'main.o'. What is the most likely cause?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.gnu.org/software/make/manual/make.html#Options-Summary

Builds on

Unlocks

Nothing builds on this yet.