3
votes

Consider the following simple makefile:

#------------------------------#
#    List all object files     #
#------------------------------#
objects := main.o foo.o bar.o baz.o

#------------------------------#
#    Define pattern rule       #
#    for   *.c -> *.o          #
#------------------------------#
%.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@

#------------------------------#
#    Define the rule to make   #
#    the end result            #
#------------------------------#
.PHONY all
all: myApp.elf

myApp.elf: $(objects)
    $(CC) $(objects) -o myApp.elf $(LFLAGS) $(LIBS)

If you give the command make all, GNU make will start at the target myApp.elf. It looks at all the prerequisites main.o, foo.o, bar.o and baz.o and attempts to update them.

To do that, make uses the pattern rule defined in the middle of the makefile. This pattern rule expands like this:

main.o: main.c
    $(CC) -c $(CFLAGS) main.c -o main.o

foo.o: foo.c
    $(CC) -c $(CFLAGS) foo.c -o foo.o

bar.o: bar.c
    $(CC) -c $(CFLAGS) bar.c -o bar.o

baz.o: baz.c
    $(CC) -c $(CFLAGS) baz.c -o baz.o

So far, so good. But you can clearly see that dependencies (included h-files) are missing.

I found some sources that recommend the following approach:

#------------------------------#
#    List all object files     #
#    and all dependency files  #
#------------------------------#
objects := main.o foo.o bar.o baz.o
deps := $(objects:.o=.d)

#------------------------------#
#    Define pattern rule       #
#    for   *.c -> *.o          #
#------------------------------#
%.o: %.c
    $(CC) -c $(CFLAGS) -MMD -MP $< -o $@

#------------------------------#
#    Define the rule to make   #
#    the end result            #
#------------------------------#
.PHONY all
all: myApp.elf

myApp.elf: $(objects)
    $(CC) $(objects) -o myApp.elf $(LFLAGS) $(LIBS)

-include $(deps)

I'm trying to wrap my head around this approach. First GNU make reads the makefile and looks for other included makefiles (see GNU make manual paragraph 3.5 "How Makefiles Are Remade"). Make attempts to update each included makefile it can find (in this case the dependency files main.d, foo.d, bar.d and baz.d) before starting execution of the makefiles. I've summarized this in the following figure:

 
enter image description here  

Exactly. I don't see any rule where the dependency files are specified as targets.

Please help me to understand what actually happens next. Please, write your answer as a step-by-step chain of events. That would be great to gain insight in the matter.

Note:
Of course, the -MMD and -MP arguments in the compilation command (see pattern rule in the middle) lead to dependency files getting created. But the targets of this pattern rule are the object-files, not the dependency-files.

Note:
I had wrongly assumed that GNU make has an implicit rule getting activated here. But thanks to your comments and answers, I now know this was wrong :-)

3

3 Answers

2
votes

Here's what happens when you run make after make clean and dependency files don't exist yet:

  1. make reads the text and reaches -include directive.
  2. For each argument of the -include directive make tries to find a rule that has it as a target and run that rule. make does this as part of processing of -include directive. No such rule will be found, because you didn't provide one.
  3. make attempts to read files specified in -include directive and just skips those which don't exist (all of them in this case).
  4. make goes to build the project, which in turn creates those *.d files.

If you run make again:

  1. [same as before]
  2. [same as before]
  3. Since files do exist now, make reads them.
  4. [same as before]

You might notice that this implies that make uses dependencies from the previous run, which is exactly how it works. Dependencies lag behind changes in the source files. This doesn't usually cause inconveniences though, and when it does removing dependency files can be used to fix the build.

1
votes

I'll answer both questions together as it makes more sense.

Make has no built-in rule for .d - it doesn't need one. -MMD -MP instructs the compiler to output dependencies as part of compilation proper.

If you think about it this is exactly what you want, as the only time you need to update a dependency file is if the relative source file has changed itself, if dependencies are generated during compilation you only need to run the compiler once to generate both the .d and .o files.

The last piece of the puzzle is -include which tells make to include dependencies if it can find them but not to worry if they aren't present, they'll be generated anyway by the compiler.

1
votes

There is no built-in make rule for .d files. You can see that that make does consider rebuilding .d files with make -d, but there is no rule, e.g:

Reading makefile 'test/debug/library.d' (search path) (don't care) (no ~ expansion)...
Updating makefiles....
 Considering target file 'test/debug/library.d'.
  File 'test/debug/library.d' does not exist.
  Looking for an implicit rule for 'test/debug/library.d'.
  No implicit rule found for 'test/debug/library.d'.
  Finished prerequisites of target file 'test/debug/library.d'.
 Must remake target 'test/debug/library.d'.
 Failed to remake target file 'test/debug/library.d'.

In the above output make checks whether library.d needs to be updated. Failing to find a rule for it, make skips updating it, see How Makefiles Are Remade:

... after reading in all makefiles, make will consider each as a goal target and attempt to update it. If a makefile has a rule which says how to update it (found either in that very makefile or in another one) or if an implicit rule applies to it, it will be updated if necessary.

And this is why it does not compile each .c twice.

You can print all the rules, including the built-ins, using make --print-data-base.