How to avoid the removal of the RPATH during CMake install step? - c++

I am working on a C++ project using CMake where I build an executable foo that uses a shared library libbar (that is being added via ExternalProject_add).
The executable build/src/foo in the build directory works perfectly fine. However, if I run make install, the installed executable /bin/foo gives me the following error.
./foo: error while loading shared libraries: libbar.so.11: cannot open shared object file: No such file or directory
I know I am not the only one with this problem (see e.g. here), and I am also aware of the handling of rpath by CMake, see here. As I understand, the install step strips the rpath variable, which explains that the library file cannot be found.
I checked this by running ldd foo in the directory /build/src/ resulting in
libbar.so => /PATH/TO/LIBBAR/libbar.so
When I run the same command in the directory /build/bin/, I get
libbar.so => not found
Now my question. How can I avoid in general that the executable "forgets" the location of the shared library during installation? I basically want the installed executable to have the same paths in rpath as the one in the build directory.
What I have tried so far
I read that you can avoid the stripping of the path via
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
However, that does not work at all. I have no idea why not, as it is the precise solution suggested here and in the documentation.
I can set the path manually of course via
SET(CMAKE_INSTALL_RPATH "$LIBBAR_PATH}/lib")
and that does work, but this solution is too specific to libbar and does e.g. not work, if I import this project in another code that also uses libbar via my project.
EDIT
I should add that this problem does not appear on all machines. I get it on Linux machines, where it also says
-- Set runtime path of "/PATH/TO/foo" to ""
during the installation. I do not get that line on my Mac, where I don't have that problem at all.
EDIT 2
I just saw that my problem is even mentioned explicitly on the documentation under Common questions. They also say that adding set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) is the solution. But it simply does not work for me, what am I doing wrong here?
EDIT 3
Could it be that the CMAKE_INSTALL_RPATH_USE_LINK_PATH = True solution does not work here, because I am adding libbar via ExternalProject? The documentation states that
CMAKE_INSTALL_RPATH_USE_LINK_PATH is a boolean that if set to true will append directories in the linker search path and outside the project to the INSTALL_RPATH. This is used to initialize the target property INSTALL_RPATH_USE_LINK_PATH for all targets.

First of all, thanks for all the comments and answers. I didn't find a perfect and simple answer to my question, but I want to share what I am going with for now.
As I understand it, the stripping of the RPATH during installation is the desired behavior, see the documentation. In general, clearing of the RPATH cannot be simply deactivated. From other questions on stackoverflow, I see that there are situations where it can work to set
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
but that did not work for me. I believe that is because in my specific setup the shared library is getting downloaded and build via ExternalProject_add. Therefore, the shared library is not from outside the project in this scenario, see here. (Please correct me if I am wrong on this.)
In the end, I don't see a better way than setting the install RPATH manually via
SET(CMAKE_INSTALL_RPATH "${LIBBAR_PATH}/lib")
If I want to import the code that uses libbar (let's call it Project_A) into another project Project_B, I will have to set the INSTALL_RPATH in the CMakeLists.txt of Project_B as well. Something along the lines of
SET(CMAKE_INSTALL_RPATH "${EXTERNAL_DIR}/Project_A/external/libbar/lib")
I don't think this is a fully satisfying soluation, but it is the best I could come up with. If anyone knows a better way, e.g. how to use the CMAKE_INSTALL_RPATH_USE_LINK_PATH option that applies to a shared library build as an ExternalProject in the same build tree, please feel free to show a better way.

Your problem is not that the build RPATH is deleted. Those RPATHs are absolute paths to your build directory and will not work once they leave your machine. You don't want them there, anyway. The problem is that you aren't setting the install RPATH correctly.
Place the following code early in your project, before any targets are created.
include(GNUInstallDirs)
if (APPLE)
set(rbase "#loader_path")
else ()
set(rbase "$ORIGIN")
endif ()
file(RELATIVE_PATH lib_dir
"${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}"
"${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}")
# Honor user overrides, whether at the command line or FetchContent
set(CMAKE_INSTALL_RPATH "${rbase};${rbase}/${libdir}"
CACHE STRING "Install RPATH")
This results in relocatable installation trees, and is sensitive to both GNU and Apple loader implementations that use different symbols for relative RPATHs.
This also assumes your install rules are standard and will install runtime objects to CMAKE_INSTALL_BINDIR and libraries to CMAKE_INSTALL_LIBDIR. Adjust these as needed.

CMake strips the rpath’s of targets installed as such:
install(
TARGETS
target_name
DESTINATION
...
)
To avoid that, install the library as file instead:
install(
FILES
$<TARGET_FILE:target_name>
DESTINATION
...
)

Related

Cmake: find_library doesn't work but find_path does (same path)

I am trying to build a cross platform school project in C++ with CMake. My project requires the use of the Irrlicht library and must compile under Linux and Windows 10.
The project source path contains a lib folder, containing the Irrlicht header (in an include subfolder), an Irrlicht.dll, an Irrlicht.lib and a FindIrrlicht.cmake module.
I set CMAKE_MODULE_PATH to point to this directory, then call find_package(Irrlicht REQUIRED) in my CMakeLists.txt.
When I try to compile under Linux, everything works fine. However, when I try to run the configuration with CMake (using the CMake GUI) under Windows, the FindIrrlicht.cmake module that I have does not work (it should, since it is provided by the school and they say it should, also I know other people had it work without modifications). I believe that I have identified the cause of the problem, but I do not understance why it occurs nor how to fix it.
FindIrrlicht.cmake looks for the include directory and the Irrlicht.lib (or libIrrlicht.so under Linux, using prefix/suffix options) in some standard Linux include/library path AND under CMAKE_MODULE_PATH. When compiling on Windows, it should find everything in CMAKE_MODULE_PATH.
It calls find_library like this:
FIND_LIBRARY(Irrlicht_LIBRARIES
NAMES
Irrlicht
PATHS
"/usr/lib64/"
"/usr/lib/"
"/usr/lib/x86_64-linux-gnu/"
"/usr/local/lib/"
"/usr/local/lib64/"
"${CMAKE_MODULE_PATH}/" #Should find in this path for Windows configuration
"${Irrlicht_DIR}/"
)
The problem is, the path in CMAKE_MODULE_PATH is correct, since find_path() (see below) is able to find the include directory for Irrlicht. But it sets the Irrlicht_LIBRARIES as NOTFOUND. I verified times and again that the files are there and that the files are in the right place. I also checked the permissions on the files.
IF (NOT Irrlicht_INCLUDE_DIRS OR NOT Irrlicht_LIBRARIES)
FIND_PATH(Irrlicht_INCLUDE_DIRS
NAMES
irrlicht.h
PATHS
"/usr/include/irrlicht/"
"/usr/local/include/irrlicht/"
"${CMAKE_MODULE_PATH}/include/" #Does find in Windows
"${Irrlicht_DIR}/include/"
)
Also, later in the CMakeLists.txt I call a file(COPY "${CMAKE_MODULE_PATH}/Irrlicht.dll" DESTINATION ${EXECUTABLE_OUTPUT_PATH}) successfully, and tried it too with Irrlicht.lib with success. So the path is definitely not the problem here.
Thank you in advance for any help !
P.S.: This is my first time asking a question on StackOverflow, if I did/wrote something not right please let me know, I would be grateful.
Well, I found my problem. I feel stupid for not thinking about this sooner...
The problem was in the pre/suffixes, with some debugging output I realised that they were set for Linux instead of Windows, hence not founding the library file.
The reason for that is that in the CMakeLists.txt, the call to find_package was done before the call to project(), thus the MSVC variables was not set during the call to find_package, which led the script to believe it was called under Linux.

Load shared libraries from lib directory in CMake?

I have downloaded a ROS2 demo from the examples repository.
Specifically, I have used minimal_subscriber and minimal_publisher.
I have ROS2 installed in /opt/ros2 and when I build these two examples with colcon build, it generates an install/ directory with lib/, shared/ and the other usual directory structure.
I can execute and run these demos perfectly fine with my current setup in my machine, but these executables link to libraries present in /opt/ros2, so when I want to execute them in another machine without ROS2 installed or I move my ROS2 installation in my machine, the executables cannot find the shared objects.
I've added a really simple script that adds all dependencies to install/lib when building but the executables don't seem to care, they aren't looking for shared libraries in the generated lib directory, they keep trying to search in /opt/ros2.
I believe this is something I should solve in CMake and it's not ROS2 specific, so, is there any way I can tell my generated executables to search in a diferent directory? (../lib or ./lib in my case)
If you are building them yourself (assumed since you mention CMake), you can set the RPATH in CMake (docs here). Specifically set the CMAKE_INSTALL_RPATH something like:
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
If you can't rebuild them, you can set LD_LIBRARY_PATH in your environment to where the libraries are located, or you can patch the executables themselves with an updated RPATH by using patchelf.
In order to get a relative RPATH rather than an absolute RPATH, use the $ORIGIN variable in your rpath spec. See "Recommendations" the the link above for more details.

How to let Cmake Install to link my executables to my target shared libraries [duplicate]

I have a question related to how to install a built executable program with cmake when it relies on some external libraries. Suppose my executable is abc, and it relies on two external libraries: lib1.so and lib2.so. The structure of the codes are as follows:
-.........
|----bin (lib1.so lib2.so)
|----include(lib1.h lib2.h)
|----src(main.cpp)
When the executable program is installed using the following cmake commands:
INSTALL(TARGETS ${Exe_Name}
RUNTIME DESTINATION Path to bin
LIBRARY DESTINATION Path to bin)
I expect that the executable program will be in the same directory with lib1.so and lib2.so. However, when I execute the built program in the installation folder, I met the following error:
error while loading shared libraries: lib1 can not open shared object file No such file or directory
If I use ldd to check the executable, I found lib1.so and lib2.so not found. After searching for possible solutions, I found if I call the executable in this way, then it worked:
LD_LIBRARY_PATH=./ ./my_program_run
Then my question is how I can let my executable program knows the locations of the shared libraries with cmake when it is installed? Thanks.
This is best solved this with the RPATH of the final executable. RPATH is a hardcoded search path for the executable itself, and allows the use of the string $ORIGIN, which expands to the location of the executable at runtime. See this reference: http://man7.org/linux/man-pages/man8/ld.so.8.html
CMake strips the rpath of a binary at installation time, to avoid the binary picking up libraries littered around your development tree. But it also provides a simple way to modify the installation rpath for exactly this reason. Here's the short answer:
IF(UNIX)
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH}:\$ORIGIN/../bin:\$ORIGIN")
ENDIF()
This particular example appends to the existing rpath, and adds . and ../bin to the search path, all relative to the location of the binary.
Some developers claim that adjusting the RPATH of the binary is not a good idea. In the ideal world, all the libraries would live in the system library directories. But if you take this to the extreme, you end up with Windows (at least the older ones), where c:\windows\system32 is full of junk that came from who knows where, and may or may not conflict with other software, etc. Using rpath and installing everything in one place seems like a great solution.
If the application is to be cleanly installed to a standard linux distribution, then you should either install the supporting shared libraries into a standard location (/usr/lib), or you should add the libraries location to the ld.so config, by create an /etc/ld.so.conf.d/myprogram.conf file containing the name of the directory the libraries are in.
If the installation is temporary or more ad-hoc, then a script to set the LD_LIBRARY_PATH is suitable.
The libraries are searched in the predefined locations which includes standard library paths configured with ld.so.conf and LD_LIBRARY_PATH. You can also try to compile your app with -rpath, but it is not recommended by some developers. I suggest you to create a wrapper script which will set LD_LIBRARY_PATH and run the real application like that:
"theapp" script:
#!/bin/sh
dir="`dirname \"$0\"`"
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}"$dir"
exec "$dir/theapp.real" # your real application
The application, script and libraries should be in the same location (under bin/).

Are exported (installed) cmake targets distributable?

I'm working on a project with a lot of external dependencies, which are included in the project tree. I would like to pre-build all of the dependencies and share the importable targets within the project tree.
I was planning to use cmake install with a CMAKE_INSTALL_PREFIX in the source tree, and use CMAKE_PREFIX_PATH to reference it for find_package. However, I'm beginning to wonder how maintainable this strategy is going to be? For example here's something I noticed in one of the installed cmake scripts:
${CMAKE_PREFIX_PATH}/lib/cmake/glfw3/glfw3Targets.cmake:
set_target_properties(glfw PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
INTERFACE_LINK_LIBRARIES "/usr/lib/x86_64-linux-gnu/librt.so;/usr/lib/x86_64-linux-gnu/libm.so;dl;/usr/lib/x86_64-linux-gnu/libX11.so;-lpthread"
)
It seems really suspicious to me that all of those link libraries are fully resolved to paths on the host machine.
I guess the question is: Are cmake installs to a prefix meant to be distributable and this is just a bad example, or are they meant to be tied to the machine you "install" them on? Ie. Is prefix really meant to just relocate where on the system things are supposed to be "installed", and my hope to use it as a shared package manager likely to be problematic?
Yes, EXPORT'ed CMake targets can be "distributable", but the project should follow some principles for achieve that.
If you link with a (external) library, but do not want export file to contain absolute path to it, then do not pass absolute path directly to target_link_libraries.
In case a linked library is shipped with a compiler (e.g. m or rt), things are simple: just pass the library's name to the target_link_libraries.
In case a linked library YYY comes from other package and is detected by find_package(YYY), this implies several things:
Script FindYYY.cmake or YYYConfig.cmake should return IMPORTED target. If this is not true, you may try to wrap its results into IMPORTED target.
target_link_libraries should use this IMPORTED target.
Script XXXConfig.cmake, shipped with your project, should use find_dependency(YYY) for discover a library on a user machine.
For find_dependency(YYY) work, a user machine should have FindYYY.cmake or YYYConfig.cmake script. Alternatively, you may ship FindYYY.cmake with your project, and adjust CMAKE_MODULE_PATH variable before find_dependency() call (in your XXXConfig.cmake).

How can I use CMake to both build wxwidgets on-demand and link with it

I have the following situation:
I'm working on an application that depends on a number of third party libs, among them wxwidgets
I build the application for multiple target configurations (x86, arm, Linux, Windows) using Linux as my build host system
Due to the above mentioned multiple target configurations, I have chosen to build those third-party libs from source, using CMake's ExternalProject_Add function.
The third-party libs are built 'on-demand' at a location separate from my application's CMAKE_BINARY_DIR so that I can wipe the build tree for my application without having to rebuild the third-party libs (takes a looooong time).
The location of the third-party libs is different depending on what target configuration I build them for (obviously).
I'm quite new to CMake and the problem I currently face is this:
The source files in my application can't find the wx include files and I need to set the correct linker flags to be able to link my application against wxwidgets.
This seems to be handled by a utility 'wx-config' that provides exactly that info as output when run with either the --cppflags or --libs flag. I can not however, figure out how to catch that output and append it to the include dirs and linked libraries I setup from my CMakeLists.txt files.
So basically what I want is.
Build wxwidgets (if it doesn't exist) for the current target configuration
Run wx-config --cppflags and --libs to find out the correct include dirs and linker flags for the current target configuration
Use the info from step 2 when building targets that are my own application
So far I've tried something like this:
# Set a target-configuration-specific location
set(wxwidgetsTop ${MYPROJECT_EXTERNAL_DIR}/wxwidgets/wxwidgets_${MYPROJECT_CURRENT_TARGET_CFG})
# Build the project
ExternalProject_Add( wxWidgetsExternal
PREFIX ${wxwidgetsTop}
URL ${MYPROJECT_EXTERNAL_DIR}/tarballs/wxWidgets-3.0.2.tar.bz2
SOURCE_DIR ${wxwidgetsTop}/src/wxwidgets
CONFIGURE_COMMAND ${configure_cmdline}
BUILD_COMMAND make -j${MYPROJECT_NCPU}
INSTALL_COMMAND make install
)
# Create a wxwidgets target to be used as a dependency from other code
add_library(wxWidgets IMPORTED STATIC GLOBAL)
add_dependencies(wxWidgets wxWidgetsExternal)
# (non-working) attempt to get the correct include dirs and linker
# flags for wxwidgets
add_custom_command(TARGET wxWidgetsExternal
POST_BUILD
COMMAND ${INSTALL_DIR}/bin/wx-config ARGS --cppflags
COMMENT "Running wx-config"
)
but the above does not provide a way to actually use the result from the custom command to append the cppflags and linker options when building the targets that make up my application.
What is a good way to achieve what I want?
I see three different ways of doing this:
Method 1: use find_package
Use wxWidgets as a standalone requirement for your project, and expect the devs to install it before building your project. In your CMakeLists.txt you will need to call find_package(wxWidgets), like this:
find_package(wxWidgets COMPONENTS net gl core base)
if(wxWidgets_FOUND)
include(${wxWidgets_USE_FILE})
# and for each of your dependent executable/library targets:
target_link_libraries(<YourTarget> ${wxWidgets_LIBRARIES})
endif()
This has the advantage of not rebuilding the lib if you rebuild your project, however it requires some work for your user (they need to handle the installation of wxWidgets by hand) and for you (you need to setup include paths / compile definitions / ... by hand).
Method 2: embed wxWidgets
The second option is to bundle wxWidgets in your repo (svn external or git submodule) and usually (re)write the CMakeLists.txt of this lib to be target-oriented. Then, in your top-most CMakeLists.txt, you can do the following:
# for example, if you just need core and net:
target_link_librairies(my_app PUBLIC wxWidgetsCore wxWidgetsNet)
# No need to manually setup include dirs, etc...
To make a CMakeLists.txt target-oriented, you define include directories and other compilation properties for a target, not a directory. Example:
# When defining wxWidgetsCore, for example
add_library(wxWidgetsCore ...)
target_include_directories(wxWidgetsCore PUBLIC someDir)
target_compile_definitions(wxWidgetsCore PUBLIC -pedantic)
target_link_libraries(wxWidgetsCore PUBLIC someLib)
The drawback of this approach is that rebuilding your project will trigger a rebuild of wxWidgets. However, it is possible to trick this by not using "rebuild" but "clean just my app, then build". Here is some insight on how to achieve this.
Method 3: some sort of hybrid
The big drawback of method 2 leads to the third approach: don't put wxWidgets in your project, but create a CMakeLists.txt that will "import" the lib. The idea: you ask your user for the directory where wxWidgets is installed, then this script will setup everything for your project. First, put the CMakeLists.txt here:
/your-project-root
/thirdparty
/wxWidgets
CMakeLists.txt
/dir-where-wxwidgets-is-installed
...
Now, you define an imported target:
# When defining wxWidgetsCore, for example
set(WX_INCLUDE_DIR ${USER_SPECIFIED_WX_ROOT}/include)
add_library(wxWidgetsCore IMPORTED GLOBAL)
set_property(TARGET wxWidgetsCore APPEND PROPERTY
INTERFACE_INCLUDE_DIRECTORIES ${WX_INCLUDE_DIR})
See INTERFACE_INCLUDE_DIRECTORIES and INTERFACE_LINK_LIBRARIES. You need your user to have build wxWidgets somewhere in his system, but from your point of view you just do target_link_libraries(your_app PUBLIC wxWidgets...), as in method 2. The advantage is that this approach is interchangeable with method 2 transparently, and you don't put the whole dependency in your project.
Setting cppflags and linker flags has to be done at CMake time, but you are trying to run wx-config at build time and you are not capturing its output anyway, so your add_custom_command() isn't doing anything useful other than printing things to the build tool's output.
Ideally, you would use the FindwxWidgets module CMake already provides. It requires wxWidgets to already be built (but see further below). Have a look at the CMake documentation for it and see if that at least sounds like what you are trying to achieve manually by using wx-config. If you can get FindwxWidgets to do the job for you, that would be a much cleaner approach.
Getting something to build at configure time so you can use it later on in your CMakeLists.txt file is a bit more tricky. ExternalProject_Add() downloads and builds things at build time, but you need wxWidgets to be built earlier at configure time. I wrote an article recently for how to do at least the downloading part at configure time and you should be able to adapt it to do the whole build at configure time instead. The article uses Google Test as its example and can be found here:
https://crascit.com/2015/07/25/cmake-gtest/
It would be trivial to make it put the wxWidgets build wherever you like, not just in the CMAKE_BINARY_DIR area. That would allow you to have different wxWidgets builds for each build configuration and to be able to wipe out your application's build tree independently of the wxWidgets builds.
Hope that points you in the right direction.
The solution I use checks for wxWidgets installation in the system using find_package, if it's not found, then the script downloads wxWidgets from github and links the program against downloaded library. The lib is installed in the build directory, so only the first build is slow - subsequent builds do not even check wxWidgets sources timestamps, so the process is as fast as building using preinstalled wxWidgets library.
Here's how my script does it:
It quietly checks for wxWidgets installation using find_package(wxWidgets QUIET),
If it's found, the script adds a dummy library wxWidgets_external,
If it's not, then it creates an ExternalProject named wxWidgets_external which downloads, builds and installs the library in the build dir, setting wxWidgets_ROOT_DIR to point to the wxWidgets installation dir,
Then we add another ExternalProject pointing to a folder with the main program's source files and CMakeLists.txt build script. This external projects depends on wxWidgets_external which is either a dummy library in case wxWidgets is preinstalled in the system, or an external project set up to download the library from github,
In the aforementioned CMakeLists.txt we again call find_package, this time with REQUIRED parameter and use the library the standard way (https://docs.wxwidgets.org/trunk/overview_cmake.html). Because we set up the dependencies and variables correctly, this call will use either preinstalled wxWidgets (if it's available) or the one downloaded from github.
There are more quirks to it, but that's the gist of it. The full sample code (tested on Linux, Windows and Mac) is available on github (https://github.com/lszl84/wx_cmake_template).
Also see full blog post which explains this in more detail: https://justdevtutorials.medium.com/wxwidgets-cmake-multiplatform-superbuild-4ea86c4e6eda