CMake RPATH handling

From KitwarePublic
Jump to: navigation, search

CMake gives the user complete control over the RPATH of executables and libraries created using CMake.

What is RPATH ?

If an executable foo links to the shared library bar, the library bar has to be found and loaded when the executable foo is executed. This is the job of the linker, under Linux this is usually ld.so. The linker searches a set of directories for the library bar, which will under most UNIXes have the name "libbar.so". The linker will search the libraries in the following directories in the given order:

  • RPATH - a list of directories which is linked into the executable, supported on most UNIX systems. It is ignored if RUNPATH is present.
  • LD_LIBRARY_PATH - an environment variable which holds a list of directories
  • RUNPATH - same as RPATH, but searched after LD_LIBRARY_PATH, supported only on most recent UNIX systems, e.g. on most current Linux systems
  • /etc/ld.so.conf - configuration file for ld.so which lists additional library directories
  • builtin directories - basically /lib and /usr/lib

There are different reasons why search directories additonal to the builtin ones can be needed - a user may install a library privately into his home directory, e.g. ~/lib/, or there may be two or more versions of the same library installed, e.g. /opt/kde3/lib/libkdecore.so and /opt/kde4/lib/libkdecore.so.

For the first case it would work if the user would set LD_LIBRARY_PATH accordingly:

export LD_LIBRARY_PATH=$HOME/lib:$LD_LIBRARY_PATH

This will break for the second case, where for some programs /opt/kde3/lib has to be searched and for other applications /opt/kde4/lib has to be searched, but in no case both. The only way to have an executable-dependent library search path is by using RPATH (or RUNPATH, but this isn't supported everywhere).

Mac OS X and the RPATH

Unlike other UNIXes, the Darwin linker, dyld, locates dependent dynamic libraries using the full path to each dylib. For example, in an executable "foo", the full paths recorded are the install names for each dependent dylib. And the library "/usr/lib/libSystem.dylib" has an install name of "/usr/lib/libSystem.B.dylib" as given by "otool -D". When linked into "foo", "foo" has a dependency on "/usr/lib/libSystem.B.dylib". This dependency can be seen with "otool -L foo". For relocatable binaries, @executable_path, @loader_path and @rpath are available to use. In the "foo" example, @executable_path and @loader_path are substituted for the location of "foo". @rpath is substituted with the RPATHs in "foo" to locate dependent dylibs. Thus the RPATH mechanism comes into play. The linker will search for @rpath/ dependencies in the following order:

  • DYLD_LIBRARY_PATH - an environment variable which holds a list of directories
  • RPATH - a list of directories which is linked into the executable. These can contain @loader_path and @executable_path.
  • builtin directories - /lib /usr/lib
  • DYLD_FALLBACK_LIBRARY_PATH - an environment variable which holds a list of directories

CMake and the RPATH

With CMake the developer has full control over the RPATH of his executables and shared libraries. This is controlled over various target properties, see the documentation of SET_TARGET_PROPERTIES(). These properties are initialized from a set of global CMake variables with the respective name. There is not something like the one "correct" RPATH set up, it depends on the wishes of the developer or distribution policies.

Different RPATH settings may be required when running a program from the build tree and when running it from its install location. If the program links to shared libraries which have been also built in the same project, then the RPATH needs to point to the directories in the build tree when running the executable from the build tree, and it must not point to the build tree anymore once the executable has been installed. Unfortunately there is no easy and fast way to change the RPATH of an existing executable or shared library. Since version 2.6 CMake builds the executable with the RPATH for the build tree, and adds padding to the RPATH if the RPATH for the install tree is longer. This way, when installing the executable (or library), CMake can change the RPATH inside the existing executable. Since version 2.8.12, CMake provides the MACOSX_RPATH target property to enable RPATH on Mac OS X. It will be enabled for both the build tree and install tree. If INSTALL_NAME_DIR is set, RPATH will be overriden for the install tree.

Before CMake 2.6 CMake had to link the executable (or library) again at install time, so that the installed executable had the modified RPATH.

If you want the same RPATH settings for executables and shared libraries in your source tree, all you have to do is to set the CMake variables CMAKE_INSTALL_RPATH, CMAKE_SKIP_BUILD_RPATH, CMAKE_BUILD_WITH_INSTALL_RPATH, CMAKE_INSTALL_RPATH_USE_LINK_PATH once, e.g. in the top level CMakeLists.txt.

Default RPATH settings

By default if you don't change any RPATH related settings, CMake will link the executables and shared libraries with full RPATH to all used libraries in the build tree. When installing, it will clear the RPATH of these targets so they are installed with an empty RPATH. On Mac OS X, with MACOSX_RPATH on, the install names for dylibs will include "@rpath/" prefix. This is equivalent to:

# 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) 

# the RPATH to be used when installing
SET(CMAKE_INSTALL_RPATH "")

# don't 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 FALSE)

With these settings you are able to run the programs directly from the build tree. Once installed, you have to ensure that they find the required shared libraries (e.g. by setting LD_LIBRARY_PATH, or by installing shared libraries into one of the default library directories).

Always full RPATH

In many cases you will want to make sure that the required libraries are always found independent from LD_LIBRARY_PATH and the install location. Then you can use these settings:

# 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)


# the RPATH to be used when installing, but only if it's not a system directory
LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir)
IF("${isSystemDir}" STREQUAL "-1")
   SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
ENDIF("${isSystemDir}" STREQUAL "-1")

With these settings you will be able to execute your programs from the build tree and they will find the shared libraries in the build tree and also the shared libraries outside your project, when installing all executables and shared libraries will be relinked, so they will find all libraries they need.

CMAKE_INSTALL_RPATH_USE_LINK_PATH is an interesting and very useful option. When building a target with RPATH, CMake determines the RPATH by using the directories of all libraries to which this target links. Some of these libraries may be located in the same build tree, e.g. libbar.so, these directories are also added to the RPATH. If this option is enabled, all these directories except those which are also in the build tree will be added to the install RPATH automatically. The only directories which may then still be missing from the RPATH are the directories where the libraries from the same project (i.e. libbar.so) are installed to. If the install directory for the libraries is not one of the systems default library directories, you have to add this directory yourself to the install RPATH by setting CMAKE_INSTALL_RPATH accordingly.

If your library is created and installed like this:

ADD_LIBRARY(bar SHARED bar.c blub.c)
INSTALL(TARGETS bar DESTINATION lib)

then you have to set the CMAKE_INSTALL_RPATH as shown above. If you are using some variable as destination for the library, you have to add this to CMAKE_INSTALL_RPATH.

If you want very tight control over the RPATH in the installed executable, you can disable CMAKE_INSTALL_RPATH_USE_LINK_PATH and just set the complete RPATH yourself via CMAKE_INSTALL_RPATH.


No RPATH at all

There is a builtin CMake option CMAKE_SKIP_RPATH, if it is enabled all other RPATH related options are ignored, no RPATH is built into anything.


Different RPATH settings within one project

Using the CMake variables as shown above you can get a global setup for RPATH for the whole project. If you need different RPATH settings for some targets, you have to use SET_TARGET_PROPERTIES() to set the respective properties, which have the same names as the variables, but without the CMAKE_ prefix.

Common questions

Q) I have created an executable like this:

 add_executable(ImageCompleter ${AppSources})
 target_link_libraries(ImageCompleter ${LibrariesAlwaysUsed})
 
 INSTALL( TARGETS ImageCompleter
   RUNTIME DESTINATION ${INSTALL_DIR} )

When I run the one that is created with 'make', it works fine. However, when I run the one that is copied by 'make install' I get something like:

 error while loading shared libraries: libwx_gtk2u_core-2.9.so.1: cannot open shared object file: No such file or directory

What do I do?

A) Add

SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)

to your CMakeLists.txt

Recommendations

$ORIGIN: On Linux/Solaris, it's probably a very good idea to specify any RPATH setting one requires to look up the location of a package's private libraries via a relative expression, to not lose the capability to provide a fully relocatable package. This is what $ORIGIN is for. In CMAKE_INSTALL_RPATH lines, it should have its dollar sign escaped with a backslash to have it end up with proper syntax in the final executable. See also the CMake and $ORIGIN discussion. For Mac OS X, there is a similar @rpath, @loader_path and @executable_path mechanism. While dependent libraries use @rpath in their install name, relocatable executables should use @loader_path and @executable_path in their RPATH. For example, you can set CMAKE_INSTALL_RPATH to @loader_path, and if an executable depends on "@rpath/libbar.dylib", the loader will then search for "@loader_path/libbar.dylib", where @rpath was effectively substituted with @loader_path.

Caveats

Since install-side RPATH tweaking is an operation that is done by target-specific installation handling, any target that should have its install RPATH changed (e.g. to CMAKE_INSTALL_RPATH) needs to end up in the installation via an install(TARGETS ...) signature and not via directory-based "copying" (otherwise the project's cmake_install.cmake file will end up without any RPATH lines).

CMake Bugs

At least on CMake 2.6.4 RHEL5, man cmakecommands for INSTALL_RPATH_USE_LINK_PATH pretends that this setting will append the link path to any CMAKE_INSTALL_RPATH content one specified. However, on this version, enabling INSTALL_RPATH_USE_LINK_PATH will replace it.

Well, not so sure about this any more: just verified this on CMake 2.8.0, and now on both versions it does list correct changes in cmake_install.cmake. This bug may have occurred due to previously not doing per-target install(), or perhaps due to some other changes in CMake RPATH-related variables.

Debugging

On Linux, one may use

objdump -x executable_or_lib.so|grep RPATH

to verify actual settings in the binary.

For verifying install-/package-time RPATH behaviour, there's often no need to go through a tiresome full (package/install package/objdump binary) cycle each time - simply analyse RPATH_CHANGE lines listed in ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake.

On Mac OS X, one may use:

  • "otool -D <file>" to view the install name of a dylib
  • "otool -L <file>" to view the dependencies
  • "otool -l <file> | grep LC_RPATH -A2" to view the RPATHs
  • "install_name_tool -id ..." to change an install name
  • "install_name_tool -change ..." to change the dependencies
  • "install_name_tool -rpath ... -add_rpath ... -delete_rpath ..." to change RPATHs