It may very well be that this is common knowledge among Linux enthusiasts, but for me, as a Windows user, it took quite a while to fully understand the concept of RPATH (Run-time Search Path). For those in the same boat, I want to share some of my newfound knowledge and of course also link to the resources which helped me better understand this topic.
The Problem
When you link a shared library (*.so
on Linux or *.dylib
on macOS), your executable needs to somehow know, where to look for said library at runtime. In most cases the library would be placed in a common system library path and the executable would find it due to a predefined list of places to search. However, in case you want to ship the shared library file with your executable or have multiple versions installed in parallel, you need to make sure, that the library can be found at your custom location.
With RPATH you can solve this issue by embedding additional search paths directly into the executable. Unfortunately, the concept gets a bit more tricky, as there are some differences between Linux and macOS.
Relativity
In a lot of cases and for maximum portability, you don’t want to specify an absolute path to the shared library, but have it relative to the executable. For that you get $ORIGIN
on Linux and @rpath
/ @loader_path
/ @executable_path
on macOS.
Linux
On Linux $ORIGIN
represents at runtime the location of the executable and thus if set the RPATH to for example $ORIGIN/lib/libmylib.so
, your executable will look for the needed shared library relative to the executable location.
macOS
On macOS @rpath
is prepended to the library path (at least when using CMake), so that when you set your custom path as RPATH, @rpath
is replaced with your own path. However macOS also provides two additional placeholders: @loader_path
and @executable_path
. As an example if the executable depends on @rpath/libmylib.dylib
and you set the RPATH to @loader_path/lib
, the final search path will be @loader_path/lib/libmylib.dylib
fulfilling the same goal as described above for Linux.
CMake Magic
After some theory let’s see how this works in practice with CMake, the defacto standard build system these days. Two general point upfront:
- To prevent old behaviors and having to deal with CMake policies, you should use CMake version >3 and judging by Repology, I would recommend a minimum of CMake 3.13.
- RPATH can also be useful during development, as you can link libraries within the build tree relative to the executable. CMake offers quite a few options to refine the behavior during build tree linking and install linking, as you will see below.
The Variables
I won’t go into details about the following list of CMake variables related to RPATH. If you want to know more, you best check out the linked documentation or resource links at the end.
CMAKE_INSTALL_RPATH
– A semicolon-separated list specifying the RPATH to use in installed targetsCMAKE_SKIP_RPATH
– If true, do not add run time path informationCMAKE_SKIP_BUILD_RPATH
– Do not include RPATHs in the build treeCMAKE_BUILD_WITH_INSTALL_RPATH
– Use the install path for the RPATHCMAKE_INSTALL_RPATH_USE_LINK_PATH
– Add paths to linker search and installed RPATHMACOSX_RPATH
– When this property is set toTRUE
, the directory portion of theinstall_name
field of this shared library will be@rpath
unless overridden byINSTALL_NAME_DIR
Default Behavior
By default RPATH will be used in the build tree, but cleared when installing the targets, leaving an empty RPATH. When MACOSX_RPATH
is set on macOS, then the install names for dylibs will include @rpath/
as prefix.
Always Full RPATH
With the following settings you can ensure that the libraries will be found in the build tree as well when you install the targets. Note: Don’t forget to set MACOSX_RPATH
on macOS.
# use, i.e. don't skip the full RPATH for the build tree
set(CMAKE_SKIP_BUILD_RPATH FALSE)
# when building, don't use the install RPATH already
# (but later on when installing)
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
# add the automatically determined parts of the RPATH
# which point to directories outside the build tree to the install RPATH
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
For more details on the interaction of these commands check out the CMake RPATH Handling wiki page.
No RPATH
You can set CMAKE_SKIP_RPATH
to completely ignore RPATH all together.
What About Windows?
The question “How can I put the DLLs in a sub-directory?” gets asked often, unfortunately, Windows doesn’t provide a solution to this, meaning Windows doesn’t support the concept of RPATH at all. Among other things, this is also the reason why a lot of developers prefer static linking on Windows.
Great! This totally solved my problem.
I was running a simulation program with GEANT4 where CMake is used to build the executable. It works from the build directory but “cannot open shared object file” after installed. This can be fixed by adding
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)
in CMakeLists.txt.
Thanks a lot!
Glad it helped you!
Excellent post. But I have one strange problem when I use cpack -G DEB all rpaths are gone. Do you know how to keep all rpath to be relative to bin after pack. Thanks.
ldd myapp
mylib.so => not found
Widows => Windows
Thanks for the hint! I’ve updated the post :)
>Windows doesn’t provide a solution to this
I think it does though, using manifest files.
I does indeed look like one can achieve this with a manifest, even if the right documentation is hard to come by.