BeeOnRope
BeeOnRope

Reputation: 65046

Makefile that recompiles only if its dependency actually changed

Let's imagine that I have a file that is generated based not on some other file in the file system, but some external command whose output may or may not change. Can I express this concept in the makefile and have dependent targets only run if the content actually changed?

To specific, I have a file date.h which has the current date, hour and minute (but not seconds), which is generated by a rule like this:

echo "#define DATE" '"' $$(date '+%D-%H:%M') '"' > date.h

The default 'app' target depends on date.h and the whole Makefile looks like:

app: date.h
    echo "Building app"
    touch app

date.h: FORCE
    echo "#define DATE" '"' $$(date '+%D-%H:%M') '"' > date.h

FORCE:

Now the FORCE target causes date.h to be rebuild every time, which causes app to always be out of date and get rebuilt.

On the other hand, if I don't include the FORCE target, then date.h will only be rebuilt if it doesn't exist (e.g., after a clean or after initially cloning the project), but never again after that.

What I want is for date.h to be out of date only if its contents were actually changed by the echo - that is, when the minute changes, but not otherwise. Is there some way to tell make to only consider date.h changed if its actual bytes changed on disk, rather than going by the timestamp, or another way to achieve this effect?

Echoing the date to a temporary file and then conditionally updating date.h based on comparing their contents crossed my mind, but I'm not sure if it's a reasonable approach.

Upvotes: 4

Views: 5587

Answers (2)

devopsfun
devopsfun

Reputation: 1418

I found another way, how to possibly solve this problem. The idea is this: You check for a temporary file, which is saved with date and time values. So, for the first time, you get a file like tmp/YYMMDDHHMM. When you run you Makefile again, then it is checked, if this files exists. If yes, do not create a new / do not update date.h. After a minute, if you run your Makefile again, a new file tmp/YYMMDDHHMM is generated and so date.h is updated:

TMP_DIR := tmp
RUN_OR_NOT := $(shell date '+%y%m%d%H%M')

all: ${TMP_DIR}/date.h

tmp_dir:
        mkdir -p ${TMP_DIR}

${TMP_DIR}/${RUN_OR_NOT}: tmp_dir
        if [ ! -f "${TMP_DIR}/${RUN_OR_NOT}" ]; then \
                echo "" > ${TMP_DIR}/${RUN_OR_NOT}; \
        fi;

${TMP_DIR}/date.h: ${TMP_DIR}/${RUN_OR_NOT}
        # create date.h
        @echo "Run date.h target, which creates date.h"
        touch ${TMP_DIR}/date.h

.PHONY: all special

Upvotes: 0

Ross Ridge
Ross Ridge

Reputation: 39631

What you suggested doing in your last paragraph is the usual way of conditionally rebuilding targets only if a particular dependency has actually changed, rather than just if that dependency's time stamp has changed. In the rule that generates the particular dependency, you generate it as a temporary file, check to see if its contents have changed and if so replace the target of the rule with the temporary file. Notably, GCC has been doing this in its makefiles as long as I can remember. It generates source code and header files with things like tables and pattern recognizers that often don't change when one of their dependencies does. Stopping targets that are dependants of these generated files from being rebuilt can make a big difference in build times.

So for your example makefile you could do something like:

date.h: FORCE
        echo "#define DATE" '"' $$(date '+%D-%H:%M') '"' > date.h.tmp
        if test -r date.h;                                              \
        then                                                            \
                cmp date.h.tmp date.h || mv -f date.h.tmp date.h;       \
        else                                                            \
                mv date.h.tmp date.h;                                   \
        fi

You might also want to look using at the move-if-change shell script included in the GCC source.

Upvotes: 5

Related Questions