Reputation: 65046
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
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
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