Search code examples
design-patternsmakefilestaticgnurules

Makefile recreates missing dependency files only when a static pattern rule is used


Consider the makefile code in Listing 1. The goal is to generate a dependency file $(DEPDIR)/%.d for each object file %.o generated when the C compiler is invoked on line 9. Specifically, the dependency files are generated as a side-effect of invoking the C compiler on line 9 to translate a %.c file into a %.o file--i.e., line 9 emits two files: $(DEPDIR)/%.d and %.o.

Listing 1.

1   DEPDIR := deps
2
3   SOURCES.c  := $(wildcard *.c)
4   OBJS.c  := $(SOURCES.c:.c=.o)
5
6   CPP_DEPFLAGS = -MT $@ -MMD -MF $(DEPDIR)/$*.d
#
#   Other rules, variable definitions, etc. are here...
#
7   %.o : %.c
8   $(OBJS.c) : %.o : %.c $(DEPDIR)/%.d | $(DEPDIR)
9       $(COMPILE.c) $(CPP_DEPFLAGS) $(OUTPUT_OPTION) $<
10
11  $(DEPDIR) : ; mkdir -p $@
12
13  # Ensure make considers missing $(DEPDIR)/%.d files as "not updated"
14  $(DEPDIR)/%.d : ;
15
16  # [EDIT]
17  .PRECIOUS: $(DEPDIR)/%.d

This makefile code works as desired, but I don't understand why (see below). Specifically, invoking make creates the DEPDIR folder if it does not exist, and the recipe on line 9 emits the $(DEPDIR)/%.d files as well as the %.o files.

Now assume the user deletes one or more of the %.d files, or the entire DEPDIR folder. Reinvoking make rebuilds the missing dependency files as desired, but again, I don't understand why.

$ rm -fr depdir *.o
$ make # Generates DEPDIR and the $(DEPDIR)/%.d files
$ rm -fr depdir
# NB: The %.o files still exist
$ make # Regenerates DEPDIR and the $(DEPDIR)/%.d files

Now replace the static pattern rule on line 8 with the implicit rule shown in Listing 2, and otherwise leave everything else as it was:

Listing 2.

7   %.o : %.c
8   %.o : %.c $(DEPDIR)/%.d | $(DEPDIR)
9       $(COMPILE.c) $(CPP_DEPFLAGS) $(OUTPUT_OPTION) $<

When an implicit rule is used, make does not regenerate deleted/missing dependency files--which is NOT the outcome I expected:

$ rm -fr depdir *.o
$ make # Generates DEPDIR and the $(DEPDIR)/%.d files
$ rm -fr depdir
# NB: The %.o files still exist
$ make # Generates DEPDIR only; DOES NOT regenerate the $(DEPDIR)/%.d files

The static pattern rule version considers the deleted/missing prerequisite $(DEPDIR)/%.d (on line 8) to be updated (which I did NOT expect) by the rule on line 14, thus making the %.o target out-of-date, and therefore the recipe on line 9 is invoked to rebuild the %.o file.

The implicit pattern rule, on the other hand, apparently considers the deleted/missing prerequisite $(DEPDIR)/%.d (on line 8) to be not updated up-to-date (which is NOT what I expected), and therefore make considers the %.o target up-to-date and the recipe on line 9 is not invoked.

So why does the implicit pattern rule (Listing 2) not invoke the recipe line on line 9? I don't find anything in the GNU Make documentation to explain this difference between a static pattern rule and an implicit rule.


Solution

  • If you want to understand this you might consider reading Auto-dependency Generation. But to explain:

    The reason the static pattern rule works is because of line 14:

    13  # Ensure make considers missing $(DEPDIR)/%.d files as "not updated"
    14  $(DEPDIR)/%.d : ;
    

    This creates an implicit rule that says how to build a .d file giving an empty recipe. The recipe doesn't do anything but if make needs to run it make will consider any target depending on it out of date. Thus, if the file is missing make will rebuild any target that depends on it.

    Removed original incorrect answer

    OK now I tried it myself and figured it out.

    The reason your implicit rule doesn't work is that the .d files are considered intermediate files. Because they're intermediate files, if they don't exist then make doesn't try to rebuild them unless they need to be rebuilt for some other reason; here there isn't one.

    You can show that my theory is correct by first deleting all the .d files then touching the .c files to force them to be rebuilt:

    $ touch *.c
    $ rm deps/*.d
    $ make
    cc    -c -MT a.o -MMD -MF deps/a.d -o a.o a.c
    cc    -c -MT b.o -MMD -MF deps/b.d -o b.o b.c
    cc    -c -MT d.o -MMD -MF deps/d.d -o d.o d.c
    rm deps/a.d deps/d.d deps/b.d
    

    Note the last line here: that's make removing all intermediate files.

    If you check my blog post above you'll see that I recommended changing the implicit rule to an explicit rule, for you that would be something like this:

    $(SOURCES.c:%.c=$(DEPDIR)/%.d):
    

    Now all the .d files are explicit targets, and thus cannot be intermediate.

    Note the blog post I link above gives a slightly better way to handle this.