24

I'm preparing a c++ app on linux (Ubuntu 16.04) with the use of a few poco libraries which I have dynamically linked. I have project folder that consists of : include, bin, lib , src and build folders and the relevant Makefile. So far I used the following Makefile which got the libraries from /usr/local/lib

CC := g++ 

# Folders
SRCDIR := src
BUILDDIR := build
TARGETDIR := bin

# Targets
EXECUTABLE := C++_APP
TARGET := $(TARGETDIR)/$(EXECUTABLE)

SRCEXT := cpp
SOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT))
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.o))
CFLAGS := -c -Wall
INC := -I include -I /usr/local/include
LIB := -L /usr/local/lib -lPocoFoundation -lPocoNet -lPocoUtil 

$(TARGET): $(OBJECTS)
@echo " Linking..."
@echo " $(CC) $^ -o $(TARGET) $(LIB)"; $(CC) $^ -o $(TARGET) $(LIB)

$(BUILDDIR)/%.o: $(SRCDIR)/%.$(SRCEXT)
@mkdir -p $(BUILDDIR)
@echo " $(CC) $(CFLAGS) $(INC) -c -o $@ $<"; $(CC) $(CFLAGS) $(INC) -c -o $@      $<

clean:
@echo " Cleaning..."; 
@echo " $(RM) -r $(BUILDDIR) $(TARGET)"; $(RM) -r $(BUILDDIR) $(TARGET)

.PHONY: clean 

Now I'd like during running the linker to search for libraries only in project lib folder without changing LD_LIBRARY_PATH or editing ld.so.conf. So I searched and I found that this can be achieved by the linker argument -Wl,rpath,$ORIGIN. So I assume that I need to add the following statement

LDFLAGS := -Wl,-rpath,$ORIGIN/../lib

and change the the LIB statement as following:

LIB := -L $ORIGIN/../lib -lPocoFoundation -lPocoNet -lPocoUtil 

However it still get the libraries from the default directory (usr/local/lib) , since I tested it with no library on the project lib folder. What have I done wrong?

dk13
  • 1,461
  • 4
  • 20
  • 47
  • `LIB := -L $(ORIGIN)/../lib -lPocoFoundation -lPocoNet -lPocoUtil`? Note the () – skomp Feb 20 '17 at 12:47
  • @skomp The same result with $(ORIGIN). It retrieves the libraries from default folder and not the project one. – dk13 Feb 20 '17 at 12:50
  • -rpath linker option doesn't replace default library search path, but appends another entry to it. So -rpath works for situations when lib is not in the default search path. Not your case, right? Try passing full paths to you libs on linker command line. Something like `-l$ORIGIN/../lib/PocoFoundation` – Alexey Semenyuk Feb 20 '17 at 13:33
  • @AlexeySemenyuk I removed LDFLAGS and wrote LIB := -L $(ORIGIN)/../lib -l$ORIGIN/../lib/PocoFoundation but I got the printout **/usr/bin/ld: cannot find -lRIGIN/../lib/PocoFoundation** and when I wrote $ORIGIN as $(ORIGIN) it still didn't and printed **/usr/bin/ld: cannot find -l/../lib/PocoFoundation** – dk13 Feb 20 '17 at 13:48
  • @dk, You don't have ORIGIN variable defined in you makefile, so it is evaluated to empty string. – Alexey Semenyuk Feb 20 '17 at 15:32

1 Answers1

38

No, you're misunderstanding. You need to pass the literal string $ORIGIN/../lib as an argument to your linker. The $ORIGIN token is kept inside your program after it's created and when the runtime linker starts to run your program it will replace $ORIGIN with the current path that your program was invoked from. This is true even if you've copied your program somewhere else. So if you run your program as /usr/local/bin/myprogram then the runtime linker will replace $ORIGIN with /usr/local/bin. If you copy it to /opt/mystuff/libexec/myprogram then the runtime linker will replace $ORIGIN with /opt/mystuff/libexec.

In order to pass a literal $ to the command invoked by a make recipe, you have to escape the $ by doubling it: $$. Otherwise, make will see the $ as introducing a make variable or function. Remember, it's perfectly legal for a make variable to avoid the parentheses etc., if it's a single character (note, $@, $<, etc.)

So when you write -Wl,-rpath,$ORIGIN/../lib make will interpret the $O in $ORIGIN as expanding a variable named O, which is empty, giving you -Wl,-rpath,RIGIN/../lib.

Also you have to escape the $ from the shell, otherwise it will try to expand $ORIGIN as a shell variable which you don't want.

You want to do something like this:

LDFLAGS = '-Wl,-rpath,$$ORIGIN/../lib' -L/usr/local/lib
LDLIBS = -lPocoFoundation -lPocoNet -lPocoUtil

$(TARGET): $(OBJECTS)
        @echo " Linking..."
        $(CC) $^ -o $@ $(LDFLAGS) $(LDLIBS)

(I don't know why you use @ to hide the command, then echo the command... why not just take out the @ and the echo and let make show you the command?)

MadScientist
  • 92,819
  • 9
  • 109
  • 136
  • Thanks for your reply. Just one clarification I tried with none library on the project lib folder which reside inside /home/user/project/lib and the linker still found them. I also omitted -L/usr/local/lib, the same. I also replaced with -L $$ORIGIN/../lib but no luck. Is it possible for the linker to check only the /home/user/project/lib folder for the needed libraries ? – dk13 Feb 20 '17 at 15:04
  • 1
    The `-rpath` option controls finding shared libraries at RUNTIME; that is, when your program is invoked. It has nothing to do with finding libraries at LINK TIME. That's controlled by the `-L` option. These are two completely different things with no overlap; the linker won't look at the `-rpath` settings and the runtime loader will not look at directories provided by `-L`. So if you are trying to understand the runtime behavior you should just ignore the `-L` flag completely: it is irrelevant. – MadScientist Feb 20 '17 at 15:28
  • 1
    The runtime loader has an algorithm it uses to find shared libraries. You can read about this algorithm via `man ld.so` (`ld.so` is the runtime loader code). It will always look for libraries using the `DT_RPATH` first (that is what is set by the `-rpath` option). However, if the library is not found there then it will continue on looking in other places. Very likely your system is configured to find libraries in `/usr/local/lib` etc. There's no way that I'm aware of to force the runtime loader to stop looking in the default places and fail if the library is not found in `DT_RPATH` – MadScientist Feb 20 '17 at 15:33
  • Thanks a lot. That cleared my confusion. So if my libraries reside both in /home/user/project/lib and in /usr/local/lib with rpath the first location will take priority. – dk13 Feb 20 '17 at 15:55
  • If you've set `$ORIGIN/../lib` via `-rpath` and you invoke your program from a directory such as `/home/user/project/bin/myprogram`, then the runtime loader will set `$ORIGIN` to `/home/user/project/bin` and then it will expand the `DT_RPATH` to `/home/user/project/bin/../lib` which is the same as `/home/user/project/lib`. So yes, it will find your local libraries first. Note you can determine which libraries were found using the `ldd` program: `ldd /home/user/project/bin/myprogram` will print out which shared libraries will be used at runtime. – MadScientist Feb 20 '17 at 18:52
  • 1
    Good explanation for a convergence of "features" that is not obvious. – davidbak Oct 17 '19 at 20:55
  • If I have LDFLAGS with `$$ORIGIN`, how can I pass it to sub-makefiles? I'm unable to do it, it enters in the other makefile with wrong value. – Adriano dos Santos Fernandes Nov 02 '21 at 16:49
  • You have to quote it for both the shell AND for make. Maybe something like: `RPATH='$(subst $$,$$$$,$(RPATH))'` – MadScientist Nov 02 '21 at 17:54