4

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.

user157765
  • 85
  • 8
  • Since you know about `rpath` it shouldn't be too hard to find out that you need to set it to the library installation path for your executable `foo`. And that it's done with the `-rpath` linker option. Which is passed as `-Wl,-rpath ` if you use the `g++` (or `c++`) front-end program. – Some programmer dude Aug 05 '22 at 10:17
  • Hi there, Thanks for the quick reply. The thing is, the library is already getting found by the executable in the ```build``` directory. It is just the installed executable whose rpath has been cleared. I want to find a way to simply not perform that clearing. – user157765 Aug 05 '22 at 10:37
  • Perhaps you should require the third-party library to be installed first, and then use its installation directory for the rpath, by passing the option explicitly for the linker (`target_link_libraries(foo bar -Wl,-rpath,installation_path_to_bar`) – Some programmer dude Aug 05 '22 at 10:41
  • 3
    @Someprogrammerdude The whole point of CMake is to *not* having such compiler specific settings in your config... – DevSolar Aug 05 '22 at 10:44
  • 2
    @user157765 I don't quite get what makes `CMAKE_INSTALL_RPATH` not work for you. It (together with CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) is definitely the option to have RPATH set in the installed executable as well. Libraries don't carry RPATH. Could you elaborate a bit what exactly you are looking for? – DevSolar Aug 05 '22 at 10:46
  • 1
    It works when I build this project, let's call it ```A```. However, I also import the library from ```A``` into another code ```B``` (via ```FetchContent```). Now, ```B``` also uses ```libbar``` (which is why ```A``` marks it as ```PUBLIC```). But I don't want to specify the path to ```libbar``` in project ```B``` by setting the RPATH to ```external/project_A/external/libbar/libbar.so```. Especially since the executable of ```B``` that I find in the build directory already links to the right ```libbar.so``` file. (and then forgets about that during installation). Does that make sense? – user157765 Aug 05 '22 at 10:52

3 Answers3

3

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.

Alex Reinking
  • 16,724
  • 5
  • 52
  • 86
  • Thanks Alex for your reply. Unfortunately, I still get the the same error and the library file cannot be found. I played around with your solution, but did not manage to get it to work. I also understand why in general you want the RPATH to be cleared. But in this case, I really prefer the behaviour to simply keep the RPATH during the installation, since it includes the absolute path to a library in ```/external/``` which I want to keep. I could just set it manually, but then the next project that imports my code needs to set it manually again with nested paths etc. – user157765 Aug 09 '22 at 06:55
1

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.

user157765
  • 85
  • 8
0

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
            ...
    )
ocroquette
  • 3,049
  • 1
  • 23
  • 26