The goal of my project is as follows:
From my main executable, I want to load a library (libfoo.so) that loads a second library (libbar.so).
I want to not specify either relative or absolute paths in any filename
arguments that I pass to dlopen
: i.e. I'd like my code to read "dlopen("libfoo.so", RTLD_LAZY)
" and not "/path/to/libfoo.so"
or "../to/libfoo.so"
.
My understanding is that the way shared libraries are looked up (by libdl) is either 1) the value of the environment variable LD_LIBRARY_PATH
, 2) "embedding" RPATH in binaries, 3) certain standard directories known to libdl.
My project's directory structure is like so:
.
├── CMakeLists.txt
├── build # this directory exists to perform an "out-of-source" build with "cmake .."
├── libs
│ ├── CMakeLists.txt
│ ├── bar.c
│ └── foo.c
└── main.c
main.c can successfully do dlopen("libfoo.so", RTLD_LAZY)
I did this by adding a target_link_directories(main PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libs)
statement in the CMakeLists.txt that compiles main.c.
This seems to have the effect of adding RPATH in the main executable, as desired:
$ objdump -x ./main | grep PATH
RUNPATH /home/user/code/scratch/3/build/libs
But foo.c is unsuccessful doing dlopen("libbar.so", RTLD_LAZY)
even though I've added a target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
statement in its CMakeLists.txt.
I notice that in spite of having added the target_link_directories
statement, libfoo.so does not have a RPATH:
$ objdump -x ./libs/libfoo.so | grep PATH
$
Things I've tried
Seemingly, RPATH is not added to shared libraries unless there is at least one target_link_libraries
statement -- even if it's an "unnecessary" library.
I.e. if I link libfoo.so with libbar.so, then libfoo.so has the desired RPATH:
# Linking libbar works, but I'd prefer not to do this:
target_link_libraries(foo bar)
...results in:
$ objdump -x ./libs/libfoo.so | grep PATH
RUNPATH /home/user/code/scratch/3/build/libs
...also if I link an "unnecessary" shared lib along with the target_link_directories
statement, then also, libfoo.so has the desired RPATH:
# Linking libbar works, but I'd prefer not to do this:
# target_link_libraries(foo bar)
# Linking an unnecessary library, then doing target_link_directories also works:
target_link_libraries(foo dl)
target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
$ objdump -x ./libs/libfoo.so | grep PATH
RUNPATH /home/user/code/scratch/3/build/libs
Questions
Do I understand CMake's behavior correctly: target_link_directories
statements only result in a corresponding RPATH entry in the shared library if there is at least one target_link_library
statement (even for an "unnecessary" lib) for the shared lib?
If that is correct, can someone please explain the rationale?
Is there another, "cleaner" way to add a RPATH to a shared lib (preferably with target_link_directories
) without any "unnecessary" statements (like target_link_library
to unnecessary libraries)?
The code/files:
// main.c
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
void* handle = dlopen("libfoo.so", RTLD_LAZY);
if (!handle) {
printf("dlopen error: %s\n", dlerror());
return EXIT_FAILURE;
}
{
void (*fptr)() = dlsym(handle, "func");
if (fptr) { fptr(); }
}
dlclose(handle);
return EXIT_SUCCESS;
}
// libs/foo.c
#include <dlfcn.h>
#include <stdio.h>
void func() {
void* handle = dlopen("libbar.so", RTLD_LAZY);
printf("here in libfoo!\n");
if (!handle) {
printf("dlopen error: %s\n", dlerror());
return;
}
{
void (*fptr)() = dlsym(handle, "func");
if (fptr) { fptr(); }
}
dlclose(handle);
}
// libs/bar.c
#include <stdio.h>
void func() {
printf("here in libbar!\n");
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(my_prj)
add_subdirectory(libs)
add_executable(main main.c)
target_link_libraries(main dl)
target_link_directories(main PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libs)
# libs/CMakeLists.txt
add_library(bar SHARED bar.c)
add_library(foo SHARED foo.c)
# This is what I want, but it doesn't work:
target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
# Linking libbar works, but I'd prefer not to do this:
# target_link_libraries(foo bar)
# Linking an unnecessary library, then doing target_link_directories also works:
# target_link_libraries(foo dl)
# target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})