How to make a subproject export a link directory of a library dependency

I have this dependency graph:

my_project
   |—> static linking perceptualdiff
       |—> (static linking with dynamic loading) FreeImage (built as a shared library)

I can correctly build perceptualdiff on its own. It will generate FreeImage.dll and FreeImage.lib in out/build/x64-Debug/_deps/freeimage-build.

And I can correctly build my_project as well. It will generate:

  • pdiff.lib in build/NinjaMultiConfig/test/_deps/perceptualdiff-build/Debug.
  • FreeImage.dll and FreeImage.lib in build/NinjaMultiConfig/test/_deps/freeimage-build/Debug.

However, when I try to run my project.exe, it won’t be able to find FreeImage.dll.

I understand I’m only adding pdiff.lib’s folder to my_project’s library path. How can I tell perceptualdiff’s CMake to “export” FreeImage.dll’s folder to whoever-links-against-pdiff’s library path?

FreeImage’s CMakeLists.txt:

add_definitions(-DOPJ_STATIC -DLIBRAW_NODLL)
add_library(FreeImage SHARED ${FreeImage_SRC})

perceptualdiff’s CMakeLists.txt:

include(FetchContent)
FetchContent_Declare(FreeImage
    GIT_REPOSITORY https://github.com/rturrado/FreeImage.git
    GIT_TAG "eb1a2ad20a6579854b56cc73eb97b7e1aa2c5861"
)
FetchContent_MakeAvailable(FreeImage)

add_library(pdiff lpyramid.cpp rgba_image.cpp metric.cpp)
target_include_directories(pdiff PUBLIC ${FreeImage_SOURCE_DIR}/Source)
target_link_libraries(pdiff PUBLIC FreeImage)

My project’s CMakeLists.txt:

FetchContent_Declare(perceptualdiff
    GIT_REPOSITORY https://github.com/rturrado/perceptualdiff.git
    GIT_TAG "eb58ae5ee2d30d947c04249f7b430c4953c00d6c"
)
FetchContent_MakeAvailable(
    perceptualdiff
)

add_executable(${PROJECT_NAME}_test ${app_sources})
target_include_directories(${PROJECT_NAME}_test PUBLIC
    ${perceptualdiff_SOURCE_DIR}
)
target_compile_features(${PROJECT_NAME}_test PRIVATE cxx_std_23)
target_link_libraries(${PROJECT_NAME}_test PUBLIC
    pdiff
)

Windows does not have an RPATH equivalent; the PATH environment variable just needs to be set up properly. Instead, you might consider copying the DLL as needed using file(GET_RUNTIME_DEPENDENCIES) or the $<TARGET_RUNTIME_DLLS> generator expression.

1 Like

Many thanks! Using $<TARGET_RUNTIME_DLLS> worked like a charm. Now, what would be the solution if I wanted my project to compile in non-Windows systems as well?

In case anyone wants to know the exact details of what worked for me, I just added the following lines:

  • at the CMakeLists.txt where I was creating my_project binary, and
  • after all the add_executable, target_compile_features, target_link_libraries, and target_compile_options commands:
# Copy DLLs the target depends on
add_custom_command(
    TARGET ${PROJECT_NAME}_test POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:${PROJECT_NAME}_test> $<TARGET_FILE_DIR:${PROJECT_NAME}_test>
    COMMAND_EXPAND_LISTS
)

See $<TARGET_RUNTIME_DLLS> documentation.
See GitHub commit diff.

macOS and Linux (or any ELF platform) have different strategies. On macOS, the library ID dictates this (it’s complicated, but rpath only takes effect if @rpath/ is actually used). On Linux, “rpath” is the term to look for. Basically, libraries say “I need libA.so.4” and the rpaths are searched for a library with that name.