Feature request: Inherit macOS "rpath" of a library without actually linking
Summary: Our project uses a closed source library which is added to the project with
add_library(... SHARED IMPORTED). However, we do not call
target_link_libraries because for this particular project we don't want to link the library directly, rather we load the library at runtime using
LoadLibraryA. This gives us flexibility on which version of the library we ship with a particular binary (or allows us to omit the binary— it is about three hundred megabytes, whereas our project binary is about three megabytes— and fall back on a stub implementation). On Linux, Android and Windows this works fine. On Macintosh however this creates a problem because
dlopen will usually not work unless the
rpath is configured properly, and CMake only configures the rpath for a library when you call
"Expected Behavior": To support these late-linking cases, CMake should add some sort of feature allowing a relationship to be established between a library target and a dependent target (such as an executable), such that
- The library target is a dependency of the second target, and
- The dependent target inherits the rpath of the library target according to the normal rules
…but NO OTHER steps of
target_link_libraries are performed. This could be done as an add_dependencies-like function call, or it could be done as a property ("don't link me") on the library target sort of like a mirror image of MACOSX_RPATH (although that might trip you up if you want the same library to be normally linked and rpath-only-linked in the same target).
It does appear to be currently possible to just plain add a needed rpath to an executable's target, but adding the rpath through
target_link_libraries results in subtle (and useful) nuances where different rpaths may be used in the build and install phases (BUILD_RPATH vs INSTALL_RPATH), and adding the rpath manually means missing out on access to this logic.
Background, rpath: Dynamic libraries on macintosh have an “install name” which is the absolute path the library believes it is installed at. When you link a binary against the library, the linker saves the library's “install name” into the executable to load from later. Although this path is absolute it can contain several aliases, such as “@rpath”, which dyld will expand as appropriate when the executable is run. An executable has a list of “rpath search paths” which dyld will try in turn for any dynamic library whose install name contains an @rpath. You can check a dylib’s install name using
otool -L, and you can check an executable’s linked libraries and rpath search paths with
otool -l. You can manually alter install-names and rpath search paths on an existing executable using
install_name_tool. A longer explanation of all this is here.
Background, project: I have a minimal repro below which does not depend on my project, but here is how this came up for me in practice:
I contribute to an open source VR game engine. The engine incorporates a number of gamedev libraries into a single package. We have a CMake file that builds the various libraries as subprojects and then builds the executable. When doing development on the engine itself, instead of building the package for install we will often for convenience just build the executable and then run it out of the CMake build folder.
Two of our libraries are closed source, so these are brought in with
add_library(… SHARED IMPORTED). These two libraries currently work in our engine on Windows and Android but not mac. I am trying to add mac support.
One of these libraries (Steam Audio, a sound spatializer) we create a SHARED IMPORTED target in the CMakeLists file for the benefit of installation, but only link at runtime as described above. This, again, works fine on Windows and Android because on that platform we move all shared libraries into a common directory. However on mac we have (up until now) just left the shared objects where they are when testing in the build directory, and relied on
target_link_libraries to set rpaths as appropriate. This means with Steam Audio, where we don't call
dlopen is unable to find the library.
A repro demonstrating the problem:
You can download the experimental branch of our VR engine in which I added the mac spatializer support (use branch mac-spatializer-test), or this minimal example repo (use branch steamaudio) which only links the two relevant libraries. (If you download the mainline project you must initialize all git submodules.) You also must download Steam Audio from here ("C API (ZIP)").
In either of these repos, you can test by running these commands:
(rm -rf build && mkdir build) (cd build && cmake -DLOVR_USE_STEAM_AUDIO=1 -DLOVR_STEAM_AUDIO_PATH=/Users/mcc/Downloads/spatializers/steamaudio_api ..) (cd build && cmake --build .)
(Obviously you must replace LOVR_STEAM_AUDIO_PATH with a path to your unzipped download of Steam Audio.)
Once you have built, run
./build/lovr and you will see this error:
Failed to load Steam Audio
This is printed when
Now, try editing the included CMakeLists.txt and uncommenting line 52:
If you rerun
(cd build && cmake --build .) and rerun the
./build/lovr test, it will now print:
Initialized Steam Audio
target_link_libraries line set up the rpath wiring that allowed
dlopen to work. However, this means actually linking SteamAudio, which we wanted to avoid.
For now, we will probably work around this by adding a manual file copy step to our Mac build like we already have for Windows, but if it were possible to plug into the BUILD_RPATH/INSTALL_RPATH logic that would have been more convenient and elegant.