Reputation: 6247
I was just handed a Makefile that has the following:
rpm: clean $(JARFILE)
# commands here...
As you might have guessed, the clean
target deletes the jar if it exists. Is this Makefile correct? Is there a guarantee that the clean
target will be run before the JARFILE
target? I'm not asking about style, but correctness. Is there a chance the JARFILE
will be built but then just blown away by the clean
target?
I'm using gnu make so that's what I'm most interested in; I'm not sure if this is intended to be portable to other flavors of make.
Upvotes: 2
Views: 330
Reputation: 1029
Is there a guarantee that the clean target will be run before the JARFILE target?
make
in general: no, there is no such guarantee.
GNU make
:
For your specific rule, with the prereqs clean $(JARFILE)
, it isn't likely that the rest of the Makefile describes a dependency tree that gives GNU make a reason to resolve $(JARFILE)
before clean
.
It is possible in theory though. E.g. consider this Makefile
:
# --- file: Makefile ---
rpm: clean A.jar
@$(rpm_build)
A.jar: A.java
cp $^ $@
@printf "[done: '$@']\n\n"
clean: A.jar
@# Error msg if attempting to delete non-existing file, so I
@# I made sure it will exist before rm-ing it by adding A.jar
@# as a prerequisite to `clean` above. //consultant
rm A.jar
@printf "[done: '$@']\n\n"
rpm_build = @[ -e $(JARFILE) ] && echo "rpm: OK." \
|| echo "rpm: ERR: no '$(JARFILE)'"
Try to make the rpm
target:
$ touch A.java
$ gmake
cp A.java A.jar
[done: 'A.jar']
rm A.jar
[done: 'clean']
rpm: ERR: no 'A.jar'
The dependency graph of Makefile
is resolved by building A.jar
, then doing clean
, then building rpm
. With this, all prereqs are honored - GNU make was not at fault for the company's build system breaking down due to the failing rpm
target (someone else was).
It's a good idea to handle any restrictions on in what order prereqs are resolved through the dependency graph. This is clearer (not everyone knows about GNU make's intricacies), makes the Makefile (more) portable, and is more robust [2].
Below is an example. I added the intermediate target A_clean_jar
so we still have the possibility of building A.jar
without first triggering a clean
.
# --- file: Makefile ---
rpm: A_clean_jar
@$(inspect_jar)
A_clean_jar: A.java clean
$(build_jar)
@printf '[done: created pristine jar]\n\n'
A.jar: A.java
$(build_jar)
@printf '[done: created jar]\n\n'
clean:
@rm -f A.jar
@printf '[done: jar cleaned out of existence]\n\n'
build_jar = cp A.java A.jar
inspect_jar = @[ -e A.jar ] && printf "rpm: OK.\n\n" \
|| printf "rpm: ERR: no 'A.jar'\n\n"
Let's try:
$ touch A.java
$ gmake rpm
[done: jar cleaned out of existence]
cp A.java A.jar
[done: created pristine jar]
rpm: OK.
$ # so: first `clean`, then build A.jar, then the rpm target - all well.
$ # now: build target A.jar - no preceding `clear` expected:
$ touch A.java
$ gmake A.jar
cp A.java A.jar
[done: created jar]
$ # check.
A few months later, the CTO glanced over the repos after noticing a slight increase in the data center bills. A commit caught his eye, issued by some test engineer of a seemingly unrelated department, supposedly maintaining the intranet legacy PHP and sometimes helping out the secretary of the accounting assistant with Excel scripts. Recognizing the commit was made to the same file that was involved in the build system havoc some time ago, causing an expensive delay of an upcoming launch event, he had a look - and found this:
#clean
clean: A.jar
@# Added back my `clean: A.jar` above - this time I tested it
@# before pushing, and now nothing crashed. // your guy
rm A.jar
@# rm -f A.jar (avoid -f with rm - dangerous)
@printf "[done: '$@']\n\n"
Some moments later, a colleague of the CTO noticed this on his screen:
$ # let's just see how this behaves
$ gmake
java.c A.java && jar cvf A.jar A.class
[done: created jar]
[done: jar cleaned out of existence]
java.c A.java && jar cvf A.jar A.class
[done: created pristine jar]
rpm: OK.
$ less Makefile
$ git revert HEAD -m "reverting: prev. commit caused an unneccessary "\
"build (jar created twice), but nothing worse than that this time"
$ git log -2 --pretty=format:'%ae'
[email protected]
$ gh api -X DELETE /repos/admin/spacex-padctrl-prod/collaborators/[email protected]
$ >> ~/notesToSelf.txt echo 'avoid long-duration consultant contracts'
footnotes
[1] at least that's what's indicated by MadScientist's other answer (he is the maintainer of GNU make)
Upvotes: 2
Reputation: 101051
If you run make without any parallelism enabled (without -j
) then your makefile will work properly. Make does guarantee that prerequisites are built in the order that they're listed in the makefile, if they are run serially.
However if you enable -j
then both targets will probably be run in parallel and then you could run into trouble.
Upvotes: 2