7

We're having some discussion lately about the way we handle .d files for dependencies in our make-based build process. The issue has been raised that sometimes the .d files can become corrupted when builds are interrupted.

We're using the .DELETE_ON_ERROR target to ensure that if a build is interrupted or fails, the object files that it was in the process of generating are deleted. However we're also using GCC to generate .d files at compile time which would need to be deleted as well. There doesn't appear to be a straightforward way to tell make about this.

So the question is, is there a way we can coax make to delete both our object and our dependency files in the case of an error? Is there some way we can set up the rules so that it knows that both the .d and .o files are generated at the same time and need to be deleted if there's an error?

Alternately, is there something else we can do to fix the problem of corrupt .d files? One suggestion along these lines is to generate the .d files with a temporary name and have a separate post-compile step per file that copies it to the correct name.

Dan Olson
  • 22,849
  • 4
  • 42
  • 56

2 Answers2

6

Generally speaking, GNU make doesn't support targets with multiple outputs. However, there is an exception to that rule: pattern rules. If you can structure your makefile such that it uses pattern rules to generate the object files, you may be able to achieve your goals. For example:

.DELETE_ON_ERROR:

all: foo.o

%.o %.d: %.c
    @touch $*.d
    @touch $*.o
    @exit 1

You'll see that with this makefile when the "error" is detected in the rule, both the .d and the .o file are deleted. The advantage to this approach is that it more accurately expresses the dependency graph by describing how the .d file is to be generated and what rule will produce it.

Alternatively, a common paradigm in this case is just as you suggested: have GCC generate the .d file into a temporary file name and only move it into place after the GCC command has completed successfully. Usually this is accomplished with a trick of the shell:

all: foo.o

%.o: %.c
    gcc -o $@ -MMD -MF $(basename $@).d.tmp -c $< \
        && mv $(basename $@).d.tmp $(basename $@).d

Here the "magic" trick is the use of the GCC flags -MMD, which generates the dependency file as a side-effect of compilation, and -MF, which lets you specify the output name for the dependency file; and the use of the shell cmd1 && cmd2 syntax, which causes the shell to only execute cmd2 if cmd1 exits successfully.

Eric Melski
  • 16,432
  • 3
  • 38
  • 52
  • The `.DELETE_ON_ERROR:` at the top of the makefile let's me get rid of all my tempfiles. Thanks! – Martin Ueding Jan 02 '12 at 14:53
  • I'd suggest putting the mv as the second line in the recipe for the pattern rule "%.o: %.c". I don't see any change in behaviour or benefit for using the shell '&&' to put it on the first line. – R Perrin Nov 14 '13 at 18:47
  • @RichardPerrin your proposal will produce incorrect behavior if gmake is invoked with the `-i` (ignore errors) option. In that case, your version will run the `mv` command whether or not there is an error in the `gcc`, while my original will not. – Eric Melski Nov 14 '13 at 19:43
0

I got neither of Eric's examples to work properly. GCC (version 4.4) doesn't compile anything when you pass in the -MM switch so it doesn't look like you can compile and write the .d in one go. Here's what I did:

%.o: %.c
    @rm -f $@ $(patsubst %.o,%.d,$@)
    gcc -c $< -o $@
    @$(CXX) -MM -MG > $(patsubst %.o,%.d,$@)

It starts out by deleting an existing .d file, the third line which generates the new one is only executed if the second command, the actual compilation step, succeeds (Eric's && trick isn't necessary, make does this automatically). For some reason I don't understand, an existing .o file isn't automatically deleted if the compilation fails, but that was easily solved by adding $@ to the first rm.

Wim
  • 11,091
  • 41
  • 58
  • Correct, gcc will not actually compile the file if you specify only `-MM`. You must also use `-MF` as described in my original answer. – Eric Melski Apr 19 '11 at 22:03
  • @Eric I added the -MF switch as well, but the preprocessor seems to be sending no output to the compiler part of gcc so I end up with a zero-byte object file... – Wim Apr 20 '11 at 10:54
  • Sorry, I made a mistake: you want `-MMD`, not `-MM`. I corrected the original answer as well. – Eric Melski Apr 20 '11 at 17:30