This article will describe Makefile header dependencies. Makefile header dependencies provides correct incremental build.
Table of Contents
1 Necessity of Makefile header dependencies
Makefile does not check which header is included in source code. When header file is updated, source code which include the header file will not be built. For avoiding this, it needs to clean all object file and compile it again. Compile all object file will take a lot of time, especially in case of C++.
For example, please see the following case.
$ tree . ├── Makefile ├── include │ └── sample.h ├── sample └── sample.c 1 directory, 4 files
Source code sample.c include header file sample.h.
$ cat sample.c #include <stdio.h> #include <sample.h> int main(void) { return 0; }
The include path is set to CFLAGS. String of "sample" is set to PROG variable.
$ cat Makefile CFLAGS := -I. -I./include SRC := $(wildcard *.c) PROG := $(patsubst %.c,%,$(SRC)) all: $(PROG) clean: @$(RM) $(PROG)
When $(PROG) is set to dependencies of all target, Makefile will use implicit rules for building object file of $(PROG). Running make command will call cc command with CFLAGS. Running make command again will not call cc command because object file of sample is already exists.
$ make cc -I. -I./include sample.c -o sample $ make: Nothing to be done for `all'.
The following target's entry is equal with this implicit rules.
%:%.c $(CC) $(CFLAGS) $< -o $@
When updating timestamp of sample.c with touch command, make will compare timestamp of sample and sample.c, and create new sample.
$ touch sample.c $ make cc -I. -I./include sample.c -o sample
When updateing timestamp of sample.h with touch command, make does not create new sample though sample.h is newer than sample. Because sample.c includes sample.h, updating sample.h means updating sample.c, so new sample should be created.
$ touch include/sample.h $ make make: Nothing to be done for `all'.
2 $(CC) -MM option
-M option and -MM option outputs included header files recursively.
-M option outputs include header which are in default compiler search path.
$ cc -M -I./include sample.c | head sample.o: sample.c /usr/include/stdc-predef.h /usr/include/stdio.h \ /usr/include/features.h /usr/include/sys/cdefs.h \ /usr/include/bits/wordsize.h /usr/include/gnu/stubs.h \ <snip>
-MM option does not output include header which are in default -compiler search path. -MM option is useful for Makefile header dependencies.
$ cc -MM -I./include sample.c sample.o: sample.c include/sample.h
3 %.d file
Automatic creation of %.d file is recommended.
- Output "sample.o: sample.c include/sample.h" with $(CC) -MM option.
- Add sample.d to target with sed command. When updating sample.c or sample.h, this will update sample.d.
Makefile is as below. The all target depends %.d and call $(PROG) target. For creating $(PROG) file, the % target will be called and implicit rules will be used.
CFLAGS := -I. -I./include SRC := $(wildcard *.c) OBJ := $(patsubst %.c,%.o,$(SRC)) DEP := $(patsubst %.c,%.d,$(SRC)) PROG := $(patsubst %.c,%,$(SRC)) all: $(DEP) @$(MAKE) $(PROG) clean: @$(RM) $(DEP) $(OBJ) $(PROG) ifneq ($(filter clean,$(MAKECMDGOALS)),clean) -include $(DEP) endif %.d: %.c $(info GEN $@) @$(CC) -MM $(CFLAGS) $< | sed 's/\($*\)\.o[ :]*/\1.o $@ : /g' > $@ %: %.d
When updating sample.h, new sample will be created.
$ make GEN sample.d cc -I. -I./include -c -o sample.o sample.c cc sample.o -o sample $ make make[1]: `sample' is up to date. $ touch sample.c $ make GEN sample.d cc -I. -I./include -c -o sample.o sample.c cc sample.o -o sample $ touch include/sample.h $ make GEN sample.d cc -I. -I./include -c -o sample.o sample.c cc sample.o -o sample