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).
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.
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?
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.
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:
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?
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).
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.
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.