I’m writing a static library LibA which has a CMake configuration file so that it can be found with find_package. This lib is privately linking another one LibB which is also detected by a find_package command and exports LibB::LibB. This is a third party library so I cannot fiddle with it. CMake provides a FindLibB (could be OpenCV for instance).

I’m building an executable SomeApp that is privately linking LibA.

My issue is that I'm working on windows, either with clang or gcc, or with msvc. So I installed two version of LibB:

  • a msvc compliant version
  • a msys2/gcc compliant version

The msvc version has no CMake configuration file but it's path is in my user environment PATH. The msys2 version has a CMake configuration file.

My development environment is organized like this:

dev
|-CMakeUtilities
|-CMakePackages
|-LibA
|   |-CMakeLists.txt
|-SomeApp
|   |-CMakeLists.txt

In my LibA project and its unit tests, I've implemented the following logic for exporting LibA:

# logic to determined LibB location
if(NOT(DEFINED ENV{LIBB_DIR}))
    # On windows with 2 installs: an msvc-compiled and a gcc-compiled one.
    if(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
        # MSVC
        if(DEFINED ENV{LIBB_DIR_msvc})
            set(ENV{LIBB_DIR} $ENV{LIBB_DIR_msvc})
        endif() 
    elseif(DEFINED ENV{LIBB_DIR_msys2})
        # gcc with msys2
        set(ENV{LIBB_DIR} $ENV{LIBB_DIR_msys2})
    else()
        message(WARNING "LIBB NOT FOUND OR NOT PROPERLY INSTALL")
    endif()
endif()
# creating the dependency
find_package(LIBB)
target_link_libraries(${TARGET_NAME} PRIVATE LIBB::LIBB)

# includes directories
target_include_directories(${TARGET_NAME} PUBLIC
$<BUILD_INTERFACE:${INCLUDE_PATH}>
$<INSTALL_INTERFACE:${INCLUDE_PATH}>
)
target_include_directories(${TARGET_NAME} PRIVATE
$<BUILD_INTERFACE:${SRC_PATH}/${TARGET_NAME}>
)

# Generating export file
include(GenerateExportHeader)
generate_export_header(${TARGET_NAME})
file(COPY ${PROJECT_BINARY_DIR}/${TARGET_NAME}_export.h DESTINATION ${INCLUDE_PATH})

# REQUIRED_DEPENDENCIES is used to list all my dependencies for the config fil generation
set(MY_REQUIRED_DEPENDENCIES)
list(APPEND MY_REQUIRED_DEPENDENCIES LIBB)
configure_file(${DEV_DIR_REL_PATH}/ModuleConfig.cmake.in ${TARGET_NAME}Config.cmake @ONLY)
unset(MY_REQUIRED_DEPENDENCIES)

# Target output path
set_target_properties(${TARGET_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${LIB_PATH}/${CMAKE_BUILD_TYPE})
set_target_properties(${TARGET_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${LIB_PATH}/${CMAKE_BUILD_TYPE})

# Installation
install (
TARGETS ${TARGET_NAME}
EXPORT ${TARGET_NAME}Targets
LIBRARY DESTINATION ${INSTALL_LIB_PATH}/${CMAKE_BUILD_TYPE}
ARCHIVE DESTINATION ${INSTALL_LIB_PATH}/${CMAKE_BUILD_TYPE}
RUNTIME DESTINATION ${SHAREDLIB_PATH}
PUBLIC_HEADER DESTINATION ${INSTALL_INCLUDE_PATH}
)
install (
EXPORT ${TARGET_NAME}Targets
NAMESPACE ${TARGET_NAME}::
DESTINATION ${CMAKEPACKAGE_PATH}
)
# Install the ${TARGET_NAME}Config.cmake
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake"
DESTINATION ${CMAKEPACKAGE_PATH}
)

And here is ModuleConfig.cmake.in:

include(CMakeFindDependencyMacro)
foreach(_lib_ IN ITEMS @MY_REQUIRED_DEPENDENCIES@)
    find_dependency(${_lib_} REQUIRED)
endforeach()

# include the generated target file
include("${CMAKE_CURRENT_LIST_DIR}/@[email protected]")
  • DEV_DIR_REL_PATH is the relative path from CMakeLists.txt to the dev directory.
  • CMAKEPACKAGE_PATH is the full path to dev/CMakePackages
  • INCLUDE_PATH is the full path, ending with includes, to LibA includes
  • LIB_PATH is the full path, ending with libs, to LibA binary
  • INSTALL_INCLUDE_PATH is just includes
  • INSTALL_LIB_PATH is just libs
  • SHAREDLIB_PATH is the full path to a single directory where I install the dynamic library I'm creating. I think it's meaningless here if LibA is a static library, but I'm trying to make my CMakeLists.txt as generic as possible.

I also set these environment variables.

  • LIBB_DIR_msvc=<PathToLibB_msvc>
  • LIBB_DIR_msys2=C:/<Msys2Path>/ucrt64/lib/cmake/

NB C:/<Msys2Path>/ucrt64/bin is also on my path.

All my projects are using set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)

This is the dependency declaration in SomeApp' CMakeLists.txt:

find_package(LibA REQUIRED)
target_link_libraries(${TARGET_NAME} PRIVATE LibA::LibA)

For sake of readability, I omitted, in my snippet, a few paths declarations that are telling were LibA files could be found.


When using msvc, LibA is correctly compiling, as its unit tests, that are running fine. But when I’m trying to build SomeApp, the linker is complaining that LibB symbols are undefined.

Looking at the build command created by CMake, I see that it is trying to link the msys2/gcc version, which is, of course, wrong. Looking at LibA CMake configuration file I only see LINK_ONLY:LibB::LibB without any references to the actual library directories.

My Guess is that, in SomeApp, CMake is doing a find_package, in CONFIG mode and it's finding the msys2 version through C:/<Msys2Path>/ucrt64/lib, though I don't know why it would look inside this directory.

Here is a sumup of the issue.

  • LibB is present as msvc and msys2 versions:
    • msvc/LibB
    • msys2/LibB
  • LibA private-depends on msvc/LibB
  • SomeApp private-depends on LibA
  • SomeApp is erroneously linked against msys2/LibB

I don’t understand how to fix it. I naively thought that when I declared a dependency, it’s path were registered also.

I though about two possible solutions:

  1. Exporting to LibA configuration file the logic to find LibB: don’t know how, risk of conflicting link in case of diamond-like dependency.
  2. Overriding FindLibB system wide (for instance, it can set the proper paths to look for LibB then call the CMake-provided FindLibB): don’t know how, risk of imposing the wrong logic to unrelated projects.

NB From the comments I understand that my configuration file is not correct. Yet my goal is to keep a CMakeLists.txt as generic as possible, so I'd like a way to handle the exception for LibB without breaking this genericity (is there a way to use a default configuration template as I'm doing, and add special-case code in it?). Besides, I realize by looking at CMake documentation for packaging (which was of no help for me, with respect to dependency management) that, though my goal was to have a relocatable package, I'm not doing it properly.

0

There are 0 best solutions below