skip to Main Content

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:

  1. default target debug is used during daily development.
  2. 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.
  3. comprel/compdbg relies on libtelopa.so, because the program uses CGO.
  4. 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


  1. It’s not overly clear what you’re after, but if I understand correctly, then you want to only build the release target iff a2ssvr 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 named a2ssvr… (note that it’s good practice to have a target name match the artifact its recipes generate). So now you would have:

    a2ssvr: libtelopa.so
        go build -ldflags="-s -w" .
        upx --best --lzma a2ssvr
    
    libtelopa.so: telop/neta/lib/libtelopa.so
        cp $< $@
    
    clean:
        cd telop/neta/lib && make clean
        git clean -fdX
    
    distclean: clean
        rm -fr a2s_*.deb
    
    deb: a2ssvr
        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
    
    .PHONY: deb clean distclean
    
    # for order only, you would need:
    # deb: libtelopa.so | a2ssvr
    

    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 the a2ssvr dependency to be order only. This would mean, however, that it would not be rebuilt if it existed, but libtelopa.so had been modified (which I am guessing is not what you want…)

    Login or Signup to reply.
  2. how to use a file as target and skip the target if that file already exists?

    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:

    a2ssvr: 
        go build -ldflags="-s -w" .
        upx --best --lzma $@
    

    GNU make provides non-standard "order-only" prerequisites as an extension. If you are willing to rely specifically on this make 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 the a2ssvr build depends on libtelopa.so, you are asking for trouble if you do not make the latter at least an order-only prerequisite:

    a2ssvr: | libtelopa.so
        go build -ldflags="-s -w" .
        upx --best --lzma $@
    

    But this sets up a situation where a2ssvr will not be rebuilt if it exists but is out of date with respect to libtelopa.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:

    a2ssvr: libtelopa.so
        go build -ldflags="-s -w" .
        upx --best --lzma $@
    

    So, let’s consider your description of the observed behavior:

    If I check-out a fresh copy from git repository, it works fine.

    Yes. This is presumably a clean build.

    However, after I make the project an executable a2ssvr is generated in the project root folder,

    Yes.

    in such case, if I run make deb again, I hope the release target is not called.

    Wait now. This is a different issue than is expressed in the question’s headline.

    However, it is called every time I do make deb.

    Yes. The release target is built because it is a prerequisite of a2ssvr, which is a prerequisite of deb, which is the target you asked for, and because no file named release exists.

    Strangely, the libtelopa.so target is NOT called if the file is already there.

    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 recursive make, which means the top-level make doesn’t have a full dependency tree to rely on. And the recursion is set up poorly, with the result that if libtelopa.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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search