Automatic header dependencies

Make · Advanced · 12 min

Hand-maintained header lists are always wrong eventually; generated dependencies keep incremental builds correct.

An object file depends on its source and every header it includes, transitively. Listing headers by hand doesn't scale and quietly goes stale. Compilers can write the list for you while compiling:

  • -MMD writes a .d file next to the object (for example build/main.d) containing a make rule build/main.o: src/main.c src/util.h ..., excluding system headers.
  • -MP adds an empty phony rule for each header, so deleting or renaming a header doesn't break the build with "No rule to make target".

Then include the generated files at the end of the Makefile:

-include $(OBJ:.o=.d)

The leading - ignores missing files: on the first build there are no .d files yet, and that's fine, because every object has to be compiled anyway. After that, each compile refreshes its .d file, so dependencies stay correct as includes change.

This works with GCC and Clang (-MMD -MP); MSVC has /showIncludes instead, one reason cross-platform C++ projects tend to use CMake. The same idea applies elsewhere: many tools can emit a dependency list (sass --watch, go list -deps) that a Makefile can include.

Add CPPFLAGS += -MMD -MP so the flags are part of the standard compile rule.

Example

CC       := cc
CPPFLAGS := -MMD -MP -Isrc
CFLAGS   := -Wall -O2
SRC      := $(wildcard src/*.c)
OBJ      := $(SRC:src/%.c=build/%.o)

build/app: $(OBJ)
	$(CC) -o $@ $^

build/%.o: src/%.c
	@mkdir -p $(@D)
	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

-include $(OBJ:.o=.d)

# build/main.d, written by the compiler:
#   build/main.o: src/main.c src/util.h
#   src/util.h:
# $ touch src/util.h && make
#   recompiles main.o and util.o (both include util.h), then relinks

Remember

  • -MMD -MP make the compiler write .d dependency files
  • -include $(OBJ:.o=.d) pulls them in and ignores missing ones
  • Headers are then tracked automatically and transitively

Watch out for

  • Omitting -MP, so deleting a header breaks the build until the .d files are removed.

Check yourself

  1. Why is -include used instead of include for the .d files?

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#Automatic-Prerequisites