Reputation: 1837
A dependency shared by multiple targets is only invoked once.
Why?
In this tiny Makefile
example, I expect deploy-everywhere
to deploy staging-repo
code to staging, and prod-repo
code to prod.
Instead, both staging and prod get staging-repo
code, which is, admittedly, suboptimal.
This seems to be happening because clone-repo
is skipped / pruned when it is examined 2nd time around, as part of deploy-to-prod
(see -d
log output below).
Why is this happening, and can I change this behavior?
$ cat Makefile
.PHONY: deploy-everywhere deploy-to-prod deploy-to-staging clone-repo
deploy-everywhere: deploy-to-staging deploy-to-prod
@echo "deployed everywhere"
deploy-to-prod: repo="prod-repo"
deploy-to-prod: clone-repo
@echo "deployed to prod (from $(repo))"
deploy-to-staging: repo="staging-repo"
deploy-to-staging: clone-repo
@echo "deployed to staging (from $(repo))"
clone-repo:
@echo "clone-repo $(repo)"
Here is the output from running make
:
$ make deploy-everywhere
clone-repo staging-repo
deployed to staging (from staging-repo)
deployed to prod (from prod-repo) <<<<< THIS IS A LIE, WE NEVER CLONED THE PROD REPO!!
deployed everywhere <<<< RIIIIGHT.
Extra logging:
$ make -d deploy-everywhere
...
Successfully remade target file `deploy-to-staging'.
Considering target file `deploy-to-prod'.
File `deploy-to-prod' does not exist.
Pruning file `clone-repo'. <<<< WHY? WHY?!?!?!?! WHY.
Finished prerequisites of target file `deploy-to-prod'.
Must remake target `deploy-to-prod'.
...
I am running GNU Make 3.81:
$ make --version
GNU Make 3.81
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
This program built for i386-apple-darwin11.3.0
Upvotes: 3
Views: 739
Reputation: 2898
To add some explanation to Beta's solution: The programmatic contract of a make
rule is that it is an opaque update of a permanent entity in the filesystem, thereby satisfying a "less-than" (younger-than) relation laid down in the dependency tree. Your construction OTOH hides the dependency from make
by passing it as a parameter. The problem that you tried to circumvent is that there is no notion for "less-than" in make
for non-filesystem-present entities (in your case, a revision control system).
The only question that I have is why you didn't simply add the clone
call to each deploy
target - if you really want to solve it as a "less-than" dependency (ie. cloning only if local repository non-existent or older) there is quite some make
programming necessary.
EDIT after your first comment:
If I got you right, your idea of PHONY
is a bit off (not totally, just a bit). PHONY
is not about facilitating something across different make
runs, it is a way to have a short-circtuit in your dependency tree. Given a chain foo -> bar -> phony_baz
(foo depending on bar depending on phony_baz) make
will, in the case that foo
was the main target, first look at bar
to see if it is older than phony_baz
which always will be the case. This is because phony_baz
itself has a make
-implicit property that will always yield a "younger_than" when it is compared as a prerequisite against a target (and now comes the important part:) at the first encounter in the dependency tree. All subsequent encounters of phony_baz
in other branches of the tree will do the exact opposite, terminating the evaluation right there at the phony target. This is no different from the behaviour at any other node in the dependency graph - make
assumes that the contract which I noted above is fulfilled and no repetitive action is justified. This is the explanation for your clone-repo
not firing a second time although it is .PHONY
- your setup is effectively a directed acyclic graph where two edges join at the same node (which make
is completely fine with, just worth noting), not a tree . Beta's first solution is to dynamically create two entirely unrelated nodes by parametrizing the rule, thereby returning the shape to a tree.
Upvotes: 1
Reputation: 99104
This is by design. Targets in a makefile are more like nouns than like verbs.
You can achieve what you want several ways. Here is one:
deploy-to-prod: clone-prod-repo
@echo "deployed to prod (from prod-repo)"
deploy-to-staging: clone-staging-repo
@echo "deployed to staging (from staging-repo)"
clone-%-repo:
@echo "clone $*-repo"
EDIT: All right, the clone
operation takes multiple parameters. Here is one way to do it:
deploy-to-prod: PARAM1=ProdOne
deploy-to-prod: PARAM2=ProdTwo
deploy-to-prod: clone-prod-repo
@echo "deployed to prod (from prod-repo)"
deploy-to-staging: PARAM1=StagingFirst
deploy-to-staging: PARAM2=StagingSecond
deploy-to-staging: clone-staging-repo
@echo "deployed to staging (from staging-repo)"
clone-%-repo:
@echo clone $*-repo with $(PARAM1) and $(PARAM2)
And here is another:
define clone-repo
@echo clone using $(1)
@echo and $(2) in some way
endef
deploy-to-prod:
@echo $@
$(call clone-repo, ProdOne, ProdTwo)
deploy-to-staging:
@echo $@
$(call clone-repo, StagingFirst, StagingSecond)
Upvotes: 1