My Makefile
looks like this (with some irrelevant targets such as ‘debug’ removed):
release: comprel
a2s: release
libtelopa.so:
cd telop/neta/lib && make && cp libtelopa.so ../../../
comprel: libtelopa.so
go build -ldflags="-s -w" .
upx --best --lzma a2ssvr
clean:
cd telop/neta/lib && make clean
git clean -fdX
distclean: clean
rm -fr a2s_*.deb
deb: a2s libtelopa.so
rm -fr debian.deb
cp libtelopa.so debian/opt/a2s/bin
cp libjson/* debian/opt/a2s/bin
cp a2ssvr debian/opt/a2s/bin/a2s
dpkg-deb --build --root-owner-group debian
rm -fr a2s*.deb
dpkg-name debian.deb
If I check-out a fresh copy from git repository, it works fine. However, after I make the project an executable a2ssvr
is generated in the project root folder, in such case, if I run make deb
again, I hope the release
target is not called. However, it is called every time I do make deb
. Strangely, the libtelopa.so
target is NOT called if the file is already there.
What is the problem, how can I avoid build the executable if it is already built?
EDIT
Based on the answers and comments, I would like to explain why the Make rules are wrote as such, let me first give out a modified Makefile:
BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
HASH=$(shell git log -n1 --pretty=format:%h)
REVS=$(shell git log --oneline|wc -l)
.PHONY: release comprel setver
debug: compdbg
release: comprel
a2ssvr: release
libtelopa.so:
cd telop/neta/lib && make && cp libtelopa.so ../../../
comprel: libtelopa.so
go build -ldflags="-s -w" . && mv a2ssvr a2s
upx --best --lzma a2ssvr
compdbg: libtelopa.so
go build -race -gcflags=all=-d=checkptr=0 . && mv a2ssvr a2s
deb: a2ssvr libtelopa.so
rm -fr debian.deb
cp libtelopa.so debian/opt/a2s/bin
cp libjson/* debian/opt/a2s/bin
cp a2ssvr debian/opt/a2s/bin/a2s
dpkg-deb --build --root-owner-group debian
rm -fr a2s*.deb
dpkg-name debian.deb
The rationale and workflow are:
- default target debug is used during daily development.
- the reason to define comprel and compdbg, rather than write the code directly under debug and release is that they share a common rule setver which write GIT revision info directly into the source code.
- comprel/compdbg relies on
libtelopa.so
, because the program uses CGO. - I want the deb target to NOT check freshness of its dependencies, given the file a2s and libtelopa.so exists they should NOT be rebuilt.
According to the answers/comments, I added .PHONY targets, but it does not work. As a matter of fact, I already tried that before intially asking the question.
I changed the a2s
target as follow:
a2s: libtelopa.so
cp verinfo.tpl version.go
sed -i 's/{_BRANCH}/$(BRANCH)/' version.go
sed -i 's/{_G_HASH}/$(HASH)/' version.go
sed -i 's/{_G_REVS}/$(REVS)/' version.go
go build -ldflags="-s -w" . && mv a2ssvr a2s
upx --best --lzma a2s
This will "partially" work, as the "a2s" target will make twice, I then change the rule to "order only":
a2s: |libtelopa.so
which worked as expected. However, I do NOT want to rewrite all build scripts in different targets!
My intention is clear: if the "a2s" file does not exist, make
should use the named rule to build it, if it does exist, that file should be used directly. To accomplish this, I now make the release
target "phony" which I think should tell make
that this target is not a real file.
2
Answers
It’s not overly clear what you’re after, but if I understand correctly, then you want to only build the
release
target iffa2ssvr
is out of date? (where release causes comprel to run, which in turn creates an artifact called a2ssvr…)If that’s the case, all you have to do is to collapse the phony targets that trigger
a2ssvr
into a target nameda2ssvr
… (note that it’s good practice to have a target name match the artifact its recipes generate). So now you would have:Now, your wording implies that you don’t want to rebuild
a2ssrv
if it exists (as opposed to when it’s out of date). If that’s the case you can switch thea2ssvr
dependency to be order only. This would mean, however, that it would not be rebuilt if it existed, butlibtelopa.so
had been modified (which I am guessing is not what you want…)If the only criterion you want to consider for whether to run the recipe for a given target is whether the target name designates an existing file, then that corresponds to excluding (ordinary) prerequisites. With GNU
make
, you should also be careful to not declare the target.PHONY
:GNU
make
provides non-standard "order-only" prerequisites as an extension. If you are willing to rely specifically on thismake
implementation (which is widely used but not universal) then you can use these to influence the order in which the recipe is run relative to other targets’ recipes. Inasmuch as your original makefile suggests that thea2ssvr
build depends onlibtelopa.so
, you are asking for trouble if you do not make the latter at least an order-only prerequisite:But this sets up a situation where
a2ssvr
will not be rebuilt if it exists but is out of date with respect tolibtelopa.so
. It sounds like you are assuming that the makefile will be used only for clean builds, so that this situation will not arise, but that’s short-sighted. Especially so when it is cleaner, clearer, and more portable to simply specify an ordinary dependency:So, let’s consider your description of the observed behavior:
Yes. This is presumably a clean build.
Yes.
Wait now. This is a different issue than is expressed in the question’s headline.
Yes. The
release
target is built because it is a prerequisite ofa2ssvr
, which is a prerequisite ofdeb
, which is the target you asked for, and because no file namedrelease
exists.That’s not strange at all, given the makefile.
libtelopa.so
is not rebuilt because it already exists, and it is not out of date relative to any of its prerequisites (because it doesn’t have any prerequisites). This target is built using recursivemake
, which means the top-levelmake
doesn’t have a full dependency tree to rely on. And the recursion is set up poorly, with the result that iflibtelopa.so
exists then it will not be rebuilt even when it ought to be. That’s not an issue for a clean build, but again, assuming clean builds is short-sighted.Some general principles:
Each file to be built should be built directly by a rule whose target matches the file’s name.
Rules should express (all the) true dependencies of their targets.
Rules should not specify prerequisites on which their targets do not actually depend. Rules for building files usually should not depend on phony targets, whether those are explicitly maked
.PHONY
or not.Minimize the use of phony targets overall, especially those that do not have established conventional meanings.