Upcoming in CMake 2.8.12: OSX RPath Support

June 10, 2013

Starting with CMake 2.8.12, there is builtin support for RPATHs on Mac OS X.  Due to the flexibility of this mechanism for finding dependent shared libraries, use of @rpath within install names should be considered over @loader_path or @executable_path.  Along with this new support, target properties such as INSTALL_RPATH, will begin to work correctly.

First off, let’s briefly review what an “install name” is, and potential problems we might have with traditional methods.  An install name is basically a path embedded within a shared library, which tells the loader where to find that shared library at runtime.  For example, if libfoo.dylib has an install name of /usr/local/lib/libfoo.dylib, the install name is copied into an executable which links with libfoo.dylib.  At runtime, the loader will search for the executable’s dependent shared libraries using the full install name, which in this case is /usr/local/lib/libfoo.dylib.  Right off, you can see that this library, as is, can only exist in /usr/local/lib.  Suppose we tried an install name of “libfoo.dylib”.  Well, then we have the option of it residing in /usr/lib or /usr/local/lib because the loader recognizes those locations.  Traditionally, to make the shared library relocatable, such as within a relocatable application bundle, we have @executable_path and @loader_path that can be used within an install name, but with those we still suffer from the constraint of a shared library residing in a path relative to the executable or a shared library using it.  Some developers write custom scripts to modify install names while copying a shared library to a new location to work around these limitations.  With @rpath, this residence constraint goes away, as well as the need to modify the install name.

When using @rpath, the install name of libfoo.dylib is simply “@rpath/libfoo.dylib” which can remain fixed for all use cases.  For frameworks, the install name can be “@rpath/foo.framework/Versions/A/foo”.  When an executable links with libfoo.dylib, the install name “@rpath/libfoo.dylib” is embedded in the executable.  When the runtime loader searches for the executable’s dependencies, it will come across @rpath/.  To interpret this, the loader needs a list of paths that can be substituted for @rpath when finding dependencies.  These paths are called relative paths (RPATH) and are embedded in the executable and used by the loader as search paths.  For example, if there is an executable bar with an RPATH of /Users/Me/MyProject/lib, and if the runtime loader encounters the dependency @rpath/libfoo.dylib, it will attempt to load “/Users/Me/MyProject/lib/libfoo.dylib”.  When using @rpath in an install name, the residence constraint goes away, and the burden for successfully locating libfoo.dylib is pushed up to the executable or shared library that uses libfoo.dylib.  While not using @executable_path and @loader_path in an install name, it is still convenient to use those variables in an RPATH for an executable to help the loader find a dependency relative to itself.

Using @rpath allows us to relocate the shared libraries anywhere.  Here are 2 examples:

  • If one creates an SDK for use by other developers, an install name of @rpath used by shared libraries means the SDK doesn’t need to be located under recognized runtime loader paths such as /usr/local/lib or /Library/Frameworks.  The burden is placed on an executable to find its dependencies, rather than a shared library emitting its location.
  • If one wanted to create an application bundle, dependent shared libraries with an @rpath install name can simply be copied into the application bundle without any modification to those shared libraries.  There is no need to run install_name_tool to fix the install names with their new location, as is done when using @executable_path or @loader_path.

Now that we know how @rpath works and why its a good idea, let’s move on to a simple example of how to use it in CMake.

Notable target properties are MACOSX_RPATH and INSTALL_RPATH.  MACOSX_RPATH is a flag that simply turns @rpath on or off for a target.  In the past, INSTALL_NAME_DIR has been used to control install names, but there shouldn’t be a need to do this anymore.  However, if it is still used, it overrides the MACOSX_RPATH flag.  For convenience, one may also set CMAKE_MACOSX_RPATH to cover multiple targets.

cmake_minimum_required(VERSION 2.8.12)
project(OSXRPath) 
 
enable @rpath in the install name for any shared library being built
# note: it is planned that a future version of CMake will enable this by default
set(CMAKE_MACOSX_RPATH 1)
 
# add a shared library
add_library(foo SHARED foo.cpp)
 
# add an executable
add_executable(bar.cpp)
# use the shared library
# note: CMake will automatically add an RPATH to this executable which is the absolute location of libfoo.dylib in the build tree.
target_link_libraries(bar foo)
 
# installation
install(TARGETS foo DESTINATION lib)
install(TARGETS bar DESTINATION bin)
 
# the install RPATH for bar to find foo in the install tree.
# if the install RPATH is not provided, the install bar will have none
set_target_properties(bar PROPERTIES INSTALL_RPATH “@loader_path/../lib”)

For debugging purposes, it is useful to know the following:

  • To view the install name of a shared library, use “otool -D <file>”
  • To view the install name of dependent shared libraries, use “otool -L <file>”
  • To view the RPATHs for locating dependent shared libraries using @rpath, use “otool -l <file> | grep LC_RPATH -A2”
  • To modify RPATHs, use install_name_tool’s -rpath, -add_rpath, and -delete_rpath options.

2 comments to Upcoming in CMake 2.8.12: OSX RPath Support

  1. You do not need to use absolute install names on Mac OS X if you do one of the following:

    1. Your library exists in $(HOME)/lib, /usr/local/lib, /lib or /usr/lib
    2. Your framework exists in /Library/Frameworks, /Network/Library/Frameworks, or /System/Library/Framework

    That list of directories can be found from man (1) dyld.
    https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/dyld.1.html

    Also, the runtime loader on Mac OS X is not configurable when compared the the GNU ld.so, so there really isn’t a way to relocate a library with an upgrade and add the new path to the runtime loader.

    Also note that system libraries and frameworks on Mac normally use a full path for the install name. For example, otool -D /usr/lib/libSystem.B.dylib gives “/usr/lib/libSystem.B.dylib”. That means the system libraries do not adhere to a principle of avoiding full paths for install names.

    Also, one other comparison with GNU’s dynamic loader, by default distributions do not include /usr/local/lib in the loader paths to avoid potential problems, whereas Mac OS X includes it.
    https://bugzilla.redhat.com/show_bug.cgi?id=144967
    So, any library on Mac OS X with an install name of the filename only, can potentially suffer from the same problems.

    So, back to the question of when not to use RPATH.

    For install names, we have these options:
    1. full path/filename
    2. filename only
    3. @rpath/filename
    4. @loader_path/filename
    5. @executable_path/filename

    I think #1 and #3 should be used. When #3 is used, RPATHs are automatically added to help the loader locate those libraries. So RPATHs should not be used when using full paths.

Leave a Reply