make install only if file changed
When writing a well crafted Makefile, one that keeps proper dependencies, and do only the minimum needed if a file has been changed, it seem that the install:
target is often overlooked. More often then not the install target looks something like:
TRG := /trg
BIN_TRG := $(TRG)/bin
ETC_TRG := $(TRG)/etc
BIN_FILES := b1 b2 b3
ETC_FILES := e1 e2 e3
install:
install $(BIN_FILES) $(BIN_TRG)
install $(ETC_FILES) $(ETC_TRG)
.PHONY: install
That is, a phony target with no dependency checking what so ever.
This kind of problem become more serious if instead of simple install there is a need to populate a staging area to prepare a binary package such as RPM packages, or if the next stage of build depends on that staging are开发者_如何学JAVAa. In this case the hole dependency tree break down after that install stage.
The question is: What is the best approach to have install rule that keeps track of dependencies?
I'm not familiar with your "install" function, and your two solutions use it different ways, but how about this:
TRG := /trg
BIN_TRG := $(TRG)/bin
ETC_TRG := $(TRG)/etc
BIN_FILES := b1 b2 b3
ETC_FILES := e1 e2 e3
INSTALLS := $(addprefix $(BIN_TRG)/,$(BIN_FILES)) \
$(addprefix $(ETC_TRG)/,$(ETC_FILES))
install: $(INSTALLS)
$(BIN_TRG)/% $(ETC_TRG)/%: %
install $< $@
.PHONY: install
EDIT: P.S. if you want the install
step to populate a staging area, or whatever, make the staging area a separate target with its own rule.
One approach I found to work is to specifically describe the dependencies of the install rule. For each file we need to construct a rule such as:
trg_dir/my_file: my_file
install $< $@
To do this, however some gmake magic is needed:
TRG := /trg
BIN_TRG := $(TRG)/bin
ETC_TRG := $(TRG)/etc
BIN_FILES := b1 b2 b3
ETC_FILES := e1 e2 e3
INSTALLS := $(addprefix $(BIN_TRG)/,$(BIN_FILES)) \
$(addprefix $(ETC_TRG)/,$(ETC_FILES))
install: $(INSTALLS)
define install_rule
$(1)/$(2): $(2)
install $(2) $(1)/$(2)
endef
$(foreach file,$(BIN_FILES),$(eval $(call install_rule,$(BIN_DIR),$(file))))
$(foreach file,$(ETC_FILES),$(eval $(call install_rule,$(ETC_DIR),$(file))))
.PHONY: install
I feel that this code need some explanation, this is also my major gripe with it - it is more complicated then such a seemingly common task should be:
The install:
target stays phony, but it will now only be used to check that the individual install rules, are up-to-date.
The rules are generated with evaluation of the result of calling a macro definition of the individual rule. We use a $(foreach ...)
function to iterate over each file, and gives the proper parameters when $(call ...)
ing the install_rule
.
I put the question and answer here, although I seemingly solved it already, because I am not happy with this solution. Any more elegant one will be eagerly accepted.
Since make doesn't support prerequisites of phony targets, you will have to create an actual dummy file on the disk and touch that file whenever install target triggers.
.PHONY: install
install: dummy
dummy: $(FILES_THAT_WERE_CHANGED)
install $(BIN_FILES) $(BIN_TRG)
install $(ETC_FILES) $(ETC_TRG)
touch dummy
精彩评论