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
msvccompliant version - a
msys2/gcccompliant 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_PATHis the relative path fromCMakeLists.txtto thedevdirectory.CMAKEPACKAGE_PATHis the full path todev/CMakePackagesINCLUDE_PATHis the full path, ending withincludes, toLibAincludesLIB_PATHis the full path, ending withlibs, toLibAbinaryINSTALL_INCLUDE_PATHis just includesINSTALL_LIB_PATHis justlibsSHAREDLIB_PATHis the full path to a single directory where I install the dynamic library I'm creating. I think it's meaningless here ifLibAis a static library, but I'm trying to make myCMakeLists.txtas 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.
LibBis present asmsvcandmsys2versions:msvc/LibBmsys2/LibB
LibAprivate-depends onmsvc/LibBSomeAppprivate-depends onLibASomeAppis erroneously linked againstmsys2/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:
- Exporting to
LibAconfiguration file the logic to findLibB: don’t know how, risk of conflicting link in case of diamond-like dependency. - Overriding
FindLibBsystem wide (for instance, it can set the proper paths to look forLibBthen call the CMake-providedFindLibB): 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.