It works by associating a .d file with each .c: for example, foo.o has a foo.d file that just contains the dependency line for foo.o, here are foo.d's contents:
# DO NOT DELETE
foo.o foo.d : foo.h header.h common.h
You'll notice one addition: this line specifies when to rebuild foo.o, but also that foo.d should be rebuilt under the same conditions---if any of the sources associated with foo.o change then foo.d gets rebuilt. foo.c isn't mentioned in this list because it's mentioned as part of the pattern rule for rebuilding a .d file (the %.d : %.c means that foo.d will get rebuilt if foo.c itself changes). foo.d was added to the dependency line created by makedepend using the sed magic above.
The final line of the Makefile includes all the .d files: the $(SRCS:.c=.d) macro transforms the list of sources in the SRCS variable by changing the extension from .c to .d. The include also tells GNU Make to check to see if the .d files need rebuilding.
GNU Make will look to see if there are rules to rebuild included Makefiles (in this case the .d files), rebuild them if necessary (following the dependencies specified in the Makefile) and then restart. This 'Makefile remaking' ( http://www.gnu.org/software/make/manual/html_node/Remaking-Makefiles.html) feature means that simply typing make will do the right thing: it'll rebuild any dependency files that need rebuilding, but only if the sources have changed, and then GNU Make will perform the build taking into account the new dependencies.
Making deleted files disappear from dependencies with $(wildcard)
Unforunately, this Makefile breaks with a fatal error if a header file is removed. If header.h is no longer needed and all references to it are removed from the .c files and the file is removed from disk, the following error occurs when make is run:
No rule to make target `header.h', needed by `foo.d'.
That happens because header.h is still mentioned in foo.d as being a prerequisite of foo.d and hence foo.d cannot be rebuilt. This catch-22 can be fixed by making the generation of foo.d smarter. The new foo.d includes the dependencies for foo.o and for foo.d separately. foo.d's dependencies are wrapped in a call to GNU Make's $(wildcard) function. Here's the new foo.d:
# DO NOT DELETE
foo.d : $(wildcard foo.h header.h common.h)
foo.o : foo.h header.h common.h
And here's the updated Makefile with a new invocation of makedepend followed by a sed line that creates the modified .d file.
all: foo.o bar.o baz.o
SRCS = foo.c bar.c baz.c
%.d : %.c
@makedepend -f - $< | sed 's,\($*\.o\)[ :]*\(.*\),$@ : $$\(wildcard \2\)\n\1 : \2,g' > $@
Now removing a header file doesn't break the Make: when foo.d is parsed the dependency line for foo.d is passed through $(wildcard). When there are no globbing symbols like * or ? in the filename, $(wildcard) ask as a simple existence filter, removing those files that don't exist from the list. So, if header.h had been removed the first line of foo.d would be equivalent to:
foo.d : foo.h common.h
and the Make would work correctly. This example Makefile now works when .c files are added (the user just updates SRCS and the new .d file is created automatically), when .c files are removed (the user updates SRCS and the old .d file is ignored), when headers are added (since that requires altering an existing .c or .h the .d file will be regenerated) and when they are removed (the $(wildcard) hides the deletion and the .d file is regenerated).
An optimized version
An optimization is to