Make my day


Suppose you have a Makefile with rules like this:

%.B: %.A
        $(COMPILER1) -o $@ $<

%.C: %.B
        $(COMPILER2) -o $@ $<

It often happens that someone makes an edit to foo.A that doesn’t have any effect on foo.B (for example, it only affects comments). Nonetheless the usual make rules mean that foo.B gets rebuilt, and that means that foo.C also gets rebuilt. I’d like to avoid the rebuilding of foo.C in this kind of circumstance, because it’s time-consuming and because further steps in the build process depend on it. I could try something like this:

%.B: %.A
        $(COMPILER1) -o $(@).tmp $< \
        && ([ ! -e $@ ] || cmp -s $@ $(@).tmp) \
        && mv $(@).tmp $@

This successfully avoids the unnecessary rebuilding of foo.C. But since it avoids updating the modification time for foo.B, the next time I run make it will go through these steps again unnecessarily. So this is not the answer (it might be acceptable if building foo.B is pretty cheap, though).

A second approach would be to use a tool like ccache to pull foo.C out of a cache when none of its dependencies have changed:

%.C: %.B
        ccache $(COMPILER2) -o $@ $<

However, ccache’s guarantee of correctness is crucially dependent on its being able to deduce all the dependencies, which it does by keying the cache on the output of the C preprocessor (thus capturing all dependencies on headers, -D options, compiler constants and so on). So ccache only helps with C and C++ compilation, not with the problem in general.

A third approach would be to switch from make to another build tool. For example, SCons “keeps track of whether a file has changed based on an MD5 checksum of the file’s contents, not the file’s modification time”. So it would do the right thing out of the box.

But let’s suppose that I have a mature build system developed using make, and I have a high level of confidence in its correctness, and I don’t want to take the risk of reimplementing the whole thing in a new build system right now, not just to get a speedup.

Any advice?

Update . It seems that I want a drop-in replacement for make that uses file contents instead of modification times to determine what needs to be recompiled. Something like makepp, maybe. I will investigate when I have time, perhaps while waiting for one of my unnecessary builds to complete…

Update . It turns out that makepp is not a drop-in replacement for GNU make. It has subtle incompatibilities in syntax and rather plaintive error messages:

target has % wildcard but no % dependencies.
  This is currently not supported.

(See GNU make manual, section 10.5.1: “A pattern rule need not have any prerequisites that contain ‘%’, or in fact any prerequisites at all. Such a rule is effectively a general wildcard. It provides a way to make any file that matches the target pattern”). And it’s written in buggy Perl:

Use of uninitialized value in scalar assignment at /usr/local/share/makepp/ line 86.
Use of uninitialized value in pattern match (m//) at /usr/local/share/makepp/ line 91.
Use of uninitialized value in length at /usr/local/share/makepp/ line 96.

So back to the drawing board.