RPATH not set, but necessary

I am linking an imported target against an executable, but the RPATH of the resulting executable is not set up correctly: when I run it, I get the ‘image not found’ error for this library. When I manually set the BUILD_RPATH target property for the executable, everything runs fine, but I was wondering why it ‘does work’ for the few other shared libraries I’m linking: Qt5, and 2 of our own, which are linked via their path e.g. target_link_libraries(foo /some/path/to/lib.so). The target that does not ‘work’ is linked as a target, e.g. target_link_libraries(foo bar::bar).

Thanks in advance,

I suspect this is macOS (based on the “image not found” mention). Libraries which have @rpath as their ID get linked in expecting to get found via an @rpath entry. However, nothing ends up setting this on CMake’s side. There’s an issue (though I can’t find it right now) about having this rpath information be a new usage requirement that would allow CMake to make the right flag decisions.

1 Like

It’s macOS! :apple:

Okay, thanks! That’s helpful. So the only option for now is to propagate the BUILD_RPATH requirements myself?

As for the other part of the question: how do the other targets achieve this? I read on the RPATH page of the wiki:

When building a target with RPATH, CMake determines the RPATH by
using the directories of all libraries to which this target links.

Shouldn’t this also happen for my bar::bar target, or is it because the LC_ID_DYLIB, the install name, contains @rpath that this mechanism doesn’t kick in or something?

Thanks again!

CMake doesn’t look at library IDs on macOS (as of now). In any case, @rpath/foo/bar/baz.dylib is possible (and done for frameworks), so a simple dirname isn’t the solution either. Other platforms don’t have this problem either by not having them (Windows) or library lookups being purely filename based (Linux and other ELF platforms) rather than supporting directory traversal like macOS does.

target_link_options is probably your best bet.

Thanks for the answers so far, very helpful!

My only remaining question is why there are some RPATHS in my executable then, but not for this one. CMake seems to use the dirname option when linking paths? I made a diff of the output of otool -l linking directly with the IMPORTED_LOCATION instead of with the target:

$ diff -u /tmp/with-{target,path}
--- /tmp/with-target	2020-03-26 09:27:02.000000000 +0100
+++ /tmp/with-path	2020-03-26 09:28:59.000000000 +0100
@@ -1,7 +1,7 @@
 assimp-driver:
 Mach header
       magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
- 0xfeedfacf 16777223          3  0x80           2    20       1712 0x00218085
+ 0xfeedfacf 16777223          3  0x80           2    21       1832 0x00218085
 Load command 0
       cmd LC_SEGMENT_64
   cmdsize 72
@@ -218,7 +218,7 @@
 Load command 9
      cmd LC_UUID
  cmdsize 24
-    uuid 01703D72-9DF2-3F50-B4B6-6B34D12D09E3
+    uuid E02E6587-E352-3DB5-9BAF-901D11700B5B
 Load command 10
        cmd LC_BUILD_VERSION
    cmdsize 32
@@ -267,14 +267,18 @@
 compatibility version 1.0.0
 Load command 17
           cmd LC_RPATH
+      cmdsize 120
+         path /Users/nick/build-circlelabsnl-Desktop_Qt_5_14_1_clang_64bit-Debug/external-libs/inst64/assimp-5.0.1/lib (offset 12)
+Load command 18
+          cmd LC_RPATH
       cmdsize 112
          path /Users/nick/build-circlelabsnl-Desktop_Qt_5_14_1_clang_64bit-Debug/external-libs/inst64/ml-0.1/bin (offset 12)
-Load command 18
+Load command 19
       cmd LC_FUNCTION_STARTS
   cmdsize 16
   dataoff 21968
  datasize 56
-Load command 19
+Load command 20
       cmd LC_DATA_IN_CODE
   cmdsize 16
   dataoff 22024

In other words: with target_link_libraries(executable path) the executable does get an RPATH as expected (and the executable can at least be started in the build dir) but with the target_link_libraries(executable target) it does not. Is this expected behavior, a bug, or is the imported target maybe missing a property or something?

Thanks again!

I think linking to paths may end up falling back on the ELF behavior. This works most of the time, but fails for frameworks that come bundled together (e.g., Qt5 binaries have an rpath at the bundle level and use directory traversal to get into Frameworks/QtCore.framework).

Cc: @brad.king

While my solution may be specific to my problem here, I figured it might be useful to document it.

In the end I made an INTERFACE library that wraps the imported library and adds INTERFACE_LINK_OPTIONS:

add_library(SphereAssimp INTERFACE)
target_link_libraries(SphereAssimp INTERFACE assimp::assimp)
target_link_options(SphereAssimp INTERFACE "LINKER:-rpath,${LIBDIR}/lib")

This nicely propagates. One caveat: even targets that are only included in “implementation files” (.cpp, .c, .cc, whatever) must be target_link_libraries(PUBLIC) for this to propagate correctly. Link options are in that sense part of the ‘interface’, and if your consumers need it down the road, they need to be exposed, even if they don’t need other properties like their INTERFACE_INCLUDE_DIRS.

Thanks again, really happy to have this working! :partying_face:

Yeah, the need to have a usage requirement piercePRIVATE dependencies for those that impose requirements on any end binary in the chain (EXECUTABLE or possibly a MODULE). I don’t know of the best way to do it.

$<LINK_ONLY:> can remove the interface or compile usage requirements at least.