Let us have a closer look at the properties set on the shared library target. We had to set the following:
- POSITION_INDEPENDENT_CODE 1: This sets the compiler flags needed for generating position-independent code. For more details, please consult https://en.wikipedia.org/wiki/Position-independent_code.
- SOVERSION ${PROJECT_VERSION_MAJOR}: This is the version of the application programming interface (API) offered by our shared library. Following semantic version, we have decided to set it to be the same as the major version of the project. CMake targets also have a VERSION property. This can be used to specify the build version of the target. Note that SOVERSION and VERSION might differ: we might want to offer multiple builds of the same API over time. We are not concerned with such granular control in this example: setting just the API version with the SOVERSION property is enough, CMake will set VERSION to the same value for us. For more details on please refer to the official documentation: https://cmake.org/cmake/help/latest/prop_tgt/SOVERSION.html
- OUTPUT_NAME "message": This tells CMake that the base name for the library is just message and not the name of the target message-shared: libmessage.so.1 will be generated when building. Proper symbolic links to libmessage.so will also be generated, as can be seen from the contents of the build directory and install prefix given previously.
- DEBUG_POSTFIX "_d": This tells CMake to add the_d postfix to the generated shared library if we are building the project in a Debug configuration.
- PUBLIC_HEADER "Message.hpp": We use this property to set a list of header files, in this case only one, defining the API functions offered by our library. This is primarily intended for Framework shared library targets on macOS but it can also be used on other operating systems and targets, as we have presently done. For more details, please see the official documentation: https://cmake.org/cmake/help/v3.6/prop_tgt/PUBLIC_HEADER.html.
- MACOSX_RPATH ON: This sets the directory portion of the “install_name” field of shared libraries to @rpath on macOS.
- WINDOWS_EXPORT_ALL_SYMBOLS ON: This will force compilation on Windows to export all symbols. Note that this is usually not a good practice and we will show in Recipe 2, Generating export headers, how to take care of symbol visibility on different platforms.
Let us now discuss RPATH. We are linking our hello-world_wDSO executable to libmessage.so.1. This means that when the executable is called, the shared library will be loaded. Thus the information on the location of the library needs to be encoded somewhere in order for the loader to do its job successfully. There are two approaches regarding the location of the library:
- It could be made known to the linker by setting environment variables:
- On GNU/Linux, this would require appending the path to the LD_LIBRARY_PATH environment variable. Note that this will most likely pollute the linker path for all applications on your system and might cause symbol clashes (https://gms.tf/ld_library_path-considered-harmful.html).
- On macOS, you can similarly set the DYLD_LIBRARY_PATH variable. This suffers from the same pitfalls as LD_LIBRARY_PATH on GNU/Linux and the situation can be ameliorated, albeit only partially, by using the DYLD_FALLBACK_LIBRARY_PATH variable instead. See the following link for an example of this: https://stackoverflow.com/a/3172515/2528668.
- It could be encoded into the executable, using the RPATH to set the run-time search path for the executable.
The latter approach is preferable and more robust. However, which path should be chosen when setting the RPATH of the dynamic shared object? We need to make sure that running the executable always finds the correct shared library, regardless of whether it is run in the build tree or in the install tree. This is achieved by setting the RPATH related properties for the hello-world_wDSO target to look for a path relative to the location of the executable itself, either via the $ORIGIN (on GNU/Linux) or @loader_path (on macOS) variables:
# Prepare RPATH
file(RELATIVE_PATH _rel ${CMAKE_INSTALL_PREFIX}/${INSTALL_BINDIR} ${CMAKE_INSTALL_PREFIX})
if(APPLE)
set(_rpath "@loader_path/${_rel}")
else()
set(_rpath "\$ORIGIN/${_rel}")
endif()
file(TO_NATIVE_PATH "${_rpath}/${INSTALL_LIBDIR}" message_RPATH)
Once the message_RPATH variable is set, the target properties will do the rest of the job:
set_target_properties(hello-world_wDSO
PROPERTIES
MACOSX_RPATH ON
SKIP_BUILD_RPATH OFF
BUILD_WITH_INSTALL_RPATH OFF
INSTALL_RPATH "${message_RPATH}"
INSTALL_RPATH_USE_LINK_PATH ON
)
Let us examine this command in detail:
- SKIP_BUILD_RPATH OFF: Tells CMake to generate an appropriate RPATH so as to be able to run the executable from within the build tree.
- BUILD_WITH_INSTALL_RPATH OFF: Turns off generating executable targets with their RPATH geared to be the same as the one for the install tree. This would prevent us from running the executable from within the build tree.
- INSTALL_RPATH "${message_RPATH}": Sets the RPATH for the installed executable target to a path previously computed.
- INSTALL_RPATH_USE_LINK_PATH ON: Tells CMake to append linker search paths to the RPATH executable.