1

I have a C++ project that produces a shared library and I want to add tests to it, in a separate executable that dynamically links with the library. So the relevant part of the Makefile looks like this:

libstuff.so: stuff.o
    $(LINK_SHARED)

test: LDLFAGS += -L$(CURDIR) -Wl,-rpath=$(CURDIR) -lstuff
test: libstuff.so
test: test.o
    $(LINK)

check:: test
    ./test

LINK_SHARED and LINK are defined to invoke the compiler in an appropriate fashion. The rpath magic is to make sure the dynamic linker links with the exact version of the library I just compiled and want to test, not with the installed one.

The problem is that by default all target-specific variables are inherited by the prerequisites, so libstuff.so gets -lstuff in its LDFLAGS and the linker complains that it can't find it (duh!).

Is there a way to say that I don't want a specific prerequisite to inherit my variables and be used for dependency purposes only?

There are some hacky workarounds that I don't like. For example I can override LDFLAGS= in the libstuff.so specification, but then it wouldn't pick up the global LDFLAGS if I decide to define them.

There's a "private" modifier on variables, which would solve my particular problem with LDFLAGS but I'd still have other things like CFLAGS inherited (and I want it to be inherited by other prerequisites like the .o files). I want something like a private modifier on a prerequisite instead.

I can make the test executable statically link with the object files I'm testing, but I actually like the fact that I'm testing that I linked the shared object properly.

I can force make to start from a clean slate variable-wise by using recursive make invocation:

.PHONY: test_prerequisites
test_prerequisites:
    $(MAKE) testlib.so

Marking the target as phony makes it execute every time (as it should, since the original make can't know about its real dependencies or it would try to make them itself). An unfortunate side effect is that the test executable itself is rebuilt every time (though it's tolerable, and at least testlib.so is not rebuilt unless necessary).

John Smith
  • 329
  • 2
  • 8
  • What is `testlib.so`? How does that relate to `test`, `test.o` and `libstuff.o`? Is `test_prerequisites` used as a prerequisite of the `test` target? If so that's why `test` rebuilds all the time. From [Phony Targets](http://www.gnu.org/software/make/manual/make.html#Phony-Targets) "A phony target should not be a prerequisite of a real target file; if it is, its recipe will be run every time make goes to update that file.". You can avoid that by making `test_prerequisites` a [Force Target](http://www.gnu.org/software/make/manual/make.html#Force-Targets) instead. – Etan Reisner Jul 17 '15 at 18:20
  • @Etan > What is testlib.so? How does that relate to test, test.o and libstuff.o? -------- It's literally in the Makefile snippet I posted, that's all there is. I edited it to include the "check" target in case people are getting confused about it being a target I specify from the command line (it is not, I do `make check`), and not the testing executable. – John Smith Jul 17 '15 at 22:04
  • @EtanReisner > You can avoid that by making test_prerequisites a Force Target instead I'm not sure what your suggestion is supposed to be here. As far as I understand it, the `FORCE` trick was used before we had `.PHONY` targets, which does exactly the same but more reliably (it works even if there is a file called `FORCE`, otherwise it does the same). – John Smith Jul 17 '15 at 22:04
  • 1
    The short answer is no, there's no way to specify that certain variables should not be inherited by certain prerequisites, other than the ones you've already mentioned (override the variable in the prerequisite that you don't want to inherit, or use `private`). – MadScientist Jul 17 '15 at 22:05
  • Also StackOverflow's version of markdown is crippled. I can't even quote you properly! – John Smith Jul 17 '15 at 22:10
  • 1
    Yes, `.PHONY` was added to be a "better" version of `FORCE` targets and, in general, that's true. In this *specific* instance it isn't. `FORCE` targets cascade normally (don't update the `FORCE` target file and things that list it as a prereq don't see it as updated) whereas `.PHONY` targets **explicitly** break that chaining (as I imagine you were running into). – Etan Reisner Jul 17 '15 at 22:18
  • Use an additional target-specific variable `libstuff.so: STUFF = -lstuff` (undefined for all other targets) and define the target-specific `test: LDLFAGS += -L$(CURDIR) -Wl,-rpath=$(CURDIR) $(STUFF)` – Come Raczy Jul 17 '15 at 22:24
  • @EtanReisner thanks, I managed to make it work: http://stackoverflow.com/a/31516142/3464516 Note that `FORCE` itself should be a `.PHONY` target, we just want that phonyness once-removed. – John Smith Jul 20 '15 at 12:12
  • I don't think you want `FORCE` to be `.PHONY` either though that probably works since you break the phoniness properties on the forced target. Personally I just always add a `FORCE: ;` target to the makefile when I need `FORCE` since that does almost the same things as `.PHONY` marking and avoids the confusion. – Etan Reisner Jul 20 '15 at 13:09
  • @Etan: of course I want it to be `.PHONY`, otherwise things will break if someone actually creates a file called `FORCE`. If you think about it, `.PHONY: target` means three things: 1. the recipe of the target is always executed, 2. the target is always considered updated, 3. therefore the recipes of the target's reverse dependencies are always executed. Consequently, if you _only_ want the recipe of some target to be always executed, which may or may not result in it actually being updated, you should give it a phony prerequisite instead of making it phony itself. – John Smith Jul 20 '15 at 16:56
  • An explicit `FORCE: ;` target as I indicated also solves the "accidental file" problem. The `;` there is also crucial. It prevents implicit rule lookup (which `.PHONY` also does). Yes, you can make `FORCE` a `.PHONY` target. I just don't know that that adds anything. You assessed correctly that the point here was to make the forced/phoniness one step further removed so you can break that and get normal behavior from your original prerequisite again. – Etan Reisner Jul 20 '15 at 17:08
  • 1
    @Etan: well, I tested it using my mock Makefile and no, FORCE doesn't solve the accidental file problem. If the file `FORCE` exists and is older than the target that depends on it then that target's recipe isn't run. Regardless of the `;` (as I understand, it's the difference between no recipe and an empty recipe, the GNU make manual doesn't use `;` by the way). – John Smith Jul 21 '15 at 09:16
  • Ah! You meant a problem in that direction. Interesting, I hadn't thought about that. Yeah, that might be an issue if the `FORCE` target doesn't touch (or remove) the file. So that makes a `.PHONY` equivalent `FORCE` target `.FORCE: ; rm -f $@` (or `touch $@` for a non-destructive option) then. And yes, I know the manual doesn't use the `;` and I'm not sure why. Let's ask MadScientist. =) – Etan Reisner Jul 21 '15 at 12:37

2 Answers2

1

Looks like it's impossible to do in a sane way, but I figured out a diabolical contraption that makes the recursive invocation solution work entirely correctly, thanks to @EtanReisner. Here's a complete runnable Makefile mock-up demonstrating the technique:

all:test

define LINK =
    @echo linking $@ with LDFLAGS=$(LDFLAGS)
    @touch $@
endef

stuff.o test.o:
    @echo compiling $@
    @touch $@

libstuff.so: stuff.o
    $(LINK)

.PHONY: FORCE

# A hack to avoid spilling LDFLAGS to prerequisites
.test_prerequisites.tmp: FORCE
    $(MAKE) .test_prerequisites2.tmp

.test_prerequisites2.tmp: libstuff.so
    touch .test_prerequisites.tmp .test_prerequisites2.tmp

test: LDFLAGS += -L$(CURDIR) -Wl,-rpath=$(CURDIR) -lstuff
test: .test_prerequisites.tmp
test: test.o
    $(LINK)

It uses two "witness" temporary files. When libstuff.so (or any other possible prerequisite) is modified, both witness files are updated and test is rebuilt.

However when libstuff.so is not updated, the first recipe is still always executed (thanks to having a phony prerequisite), then the recursive make invocation sees that the second witness doesn't need to be updated and doesn't execute its recipe, so the first witness is not updated either and test is not rebuilt.

Note: for this particular problem I decided to use the original, simpler recursive make solution because a) I happen to already have a phony target used to build all shared libraries in the project, and b) I pretty much always want the test binary to be rebuilt when running tests anyway, because I just changed something, the thing I want to test.

John Smith
  • 329
  • 2
  • 8
0

Add a Variable LDFLAGS_TEST and add it to the LINK command for test.

If you don't want to do this, see this similar question: Define make variable at rule execution time

Community
  • 1
  • 1
arved
  • 4,401
  • 4
  • 30
  • 53