I have a CMake project C that depends on project B, which in turn depends on project A. I've created a superbuild that builds all three. Project C uses headers from project B, and my CMakeLists for Project C looks like this:
cmake_minimum_required(VERSION 3.10)
project(code_c)
find_package(b REQUIRED)
add_library(c c.c)
target_link_libraries(c PUBLIC b)
target_include_directories(c INTERFACE
$<INSTALL_INTERFACE:include>)
install(TARGETS c EXPORT c)
install(FILES c.h DESTINATION include)
export(PACKAGE c)
export(TARGETS c FILE "${PROJECT_BINARY_DIR}/bTargets.cmake")
install(EXPORT c FILE aTargets.cmake DESTINATION lib/cmake/c)
include(CMakePackageConfigHelpers)
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/cConfig.cmake"
INSTALL_DESTINATION lib/cmake/c)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/cConfig.cmake"
DESTINATION lib/cmake/c)
install(EXPORT c
FILE cTargets.cmake
DESTINATION lib/cmake/c)
My CMakeLists for Project B looks like this:
cmake_minimum_required(VERSION 3.10)
project(code_b)
find_package(a REQUIRED)
add_library(b b.c)
target_link_libraries(b PUBLIC a)
target_include_directories(b INTERFACE
$<INSTALL_INTERFACE:include>)
install(TARGETS b EXPORT b)
install(FILES b.h DESTINATION include)
export(PACKAGE b)
export(TARGETS b FILE "${PROJECT_BINARY_DIR}/bTargets.cmake")
install(EXPORT b FILE aTargets.cmake DESTINATION lib/cmake/b)
include(CMakePackageConfigHelpers)
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/bConfig.cmake"
INSTALL_DESTINATION lib/cmake/b)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/bConfig.cmake"
DESTINATION lib/cmake/b)
install(EXPORT b
FILE bTargets.cmake
DESTINATION lib/cmake/b)
My Config.cmake.in for Project B looks like
#PACKAGE_INIT#
include("${CMAKE_CURRENT_LIST_DIR}/bTargets.cmake")
check_required_components(b)
When I attempt to build Project C, I get
/Users/jhaiduce/Development/cmake_include_test/build/b/include/b.h:4:10: fatal error:
'a.h' file not found
#include "a.h"
^~~~~
1 error generated.
However, if I add find_package(a REQUIRED) to the CMakeLists.txt for Project A, then the include directories from target 'a' are added to the build and Project C compiles successfully. Should this be necessary? I would have expected that CMake would export Project A's targets along with Project B, making it unneccesary to call find_package(a) from Project C. Does the fact that Project C fails to build without this indicate a problem with the way I exported Project B?
I think in BConfig.cmake.in you should use:
include(CMakeFindDependencyMacro)
if(NOT A_FOUND AND NOT TARGET a)
find_dependency(A REQUIRED)
endif()
ref:
https://cmake.org/cmake/help/latest/module/CMakeFindDependencyMacro.html
Related
I need to use an specific external shared library on a project. It links fine, and locally it runs like a charm.
The problem is when I need to make a distribution package. When I run cpack the output file has the symbolic links to these external shared libraries. I need it to include the external libraries as well (not just the symbolic link). How can I do it?
Here is my CMakeLists.txt
project(test VERSION 1.0.0 LANGUAGES CXX C)
set(CMAKE_CXX_STANDARD 11)
set(Protobuf_LIBRARIES $ENV{CONDA_PREFIX}/lib/libprotobuf.so)
add_executable(test main.cpp)
target_link_libraries(test
${Protobuf_LIBRARIES}
)
include(GNUInstallDirs)
install(TARGETS test
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(FILES ${Protobuf_LIBRARIES}
DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT RuntimeLibraries
)
set(CPACK_PACKAGE_NAME "Test")
include(CPack)
=== EDIT ===
Following #alex-reinking suggestion, and for the sake of completition on this question, here is the CMakeLists.txt working. It still has a problem with dependencies pointing to local dynamic libraries, so I needed to use an external app to solve that on OSX ( https://github.com/auriamg/macdylibbundler/ ). Be aware that, on this example, I'm including a Linux library.
project(test VERSION 1.0.0 LANGUAGES CXX C)
set(CMAKE_CXX_STANDARD 11)
set(Protobuf_LIBRARIES $ENV{CONDA_PREFIX}/lib/libprotobuf.so)
add_executable(test main.cpp)
target_link_libraries(test
${Protobuf_LIBRARIES}
)
include(GNUInstallDirs)
install(TARGETS test
RUNTIME_DEPENDENCY_SET A-dependency
)
install(RUNTIME_DEPENDENCY_SET A-dependency PRE_EXCLUDE_REGEXES )
install(FILES ${MYLIB_LOCATION} DESTINATION ${CMAKE_INSTALL_LIBDIR})
set(CPACK_PACKAGE_NAME "Test")
include(CPack)```
I have built and installed a cmake package (Foo) and was able to import it into another project (Bar) using find_package(Foo COMPONENTS A B C REQUIRED CONFIG). In this scenario, Foo::A depends on Foo::B and Foo::B is actually made up of 3 shared libraries Ba Bb Bc. In Bar when I call target_link_libraries(Bar PUBLIC Foo::A Foo::B) I get an error message "The following targets are referenced, but are missing: Foo::Bb". If I then edit find_package and target_link_libraries to include Foo::Bb I then get an error "include could not find requested file .../FooBbTargets.cmake".
The code to export/install Foo::B is below:
# using cmake 3.21
# Ba Bb Bc defined above as shared libraries
# Ba has the same name as B
# Bc is linked to Bb, Bb is linked to Ba, links are all public.
include(GNUInstallDirs)
install(
TARGETS Ba Bb Bc
EXPORT FooBTargets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install(
FILES
# list of header files
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install(
EXPORT FooBTargets
FILE FooBTargets.cmake
NAMESPACE Foo::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Foo
)
export(
EXPORT FooBTargets
FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/FooBTargets.cmake"
NAMESPACE Foo::
)
Ideally I would actually like to link to just Foo rather than having to list the components separately. It looks like my current solution is to turn each of the shared libraries in B into their own components and link to them individually but I would prefer not to.
Relevant part of top level CMakeLists.txt for Foo
# our program only needs to support windows
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(CONFIGFILEINSTALLDIR lib/cmake/Foo)
set(LIBRARY_INSTALL_DIR lib)
set(INCLUDE_INSTALL_DIR include)
include(CMakePackageConfigHelpers)
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/FooConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/FooConfig.cmake"
INSTALL_DESTINATION "${CONFIGFILEINSTALLER}"
PATH_VARS
LIBRARY_INSTALL_DIR
INCLUDE_INSTALL_DIR
)
write_basic_package_version_file(
${CMAKE_BINARY_DIR}/cmake/FooConfigVersion.cmake
VERSION ${PACKAGE_VERSION}
COMPATIBILITY SameMajorVersion
)
install(
FILES
"${CMAKE_CURRENT_BINARY_DIR}/FooConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/cmake/FooConfigVersion.cmake"
DESTINATION "${CONFIGFILEINSTALLER}"
)
FooConfig.cmake.in
#PACKAGE_INIT#
set_and_check(Foo_INCLUDE_DIRS "#PACKAGE_INCLUDE_INSTALL_DIR#")
set_and_check(Foo_LIBRARY_DIRS "#PACKAGE_LIBRARY_INSTALL_DIR#")
set(_supported_components A B C)
foreach(_comp ${Foo_FIND_COMPONENTS})
if(NOT _comp IN_LIST _supported_components)
set(Foo_FOUND False)
endif()
include("${CMAKE_CURRENT_LIST_DIR}/Foo${_comp}Targets.cmake")
endforeach()
check_required_components(Foo)
Im trying to create project in Cmake which contains components.
So another project can use this project using:
find_package(ltk COMPONENTS Core Networking REQUIRED)
target_link_libraries(test ltk::Core ltk::Networking)
My library project (called ltk)
Here is a project tree:
ltk/
|--- CMakeLists.txt
|--- ltkConfig.cmake.in
|--- ltkConfigVersion.cmake.in
|
+--- Core/
| |--- core.cc
| |--- core.h
| |--- CMakeLists.txt
|
+--- Networking/
|--- networking.cc
|--- networking.h
|--- CMakeLists.txt
Full project is here: https://gitlab.com/T0maas/cmake-testing
The file ltk/CMakeLists.txt has this content:
set(project ltk)
set(LTK_VERSION 0.0.1)
cmake_minimum_required(VERSION 3.21)
project(${project})
add_subdirectory(Core)
add_subdirectory(Networking)
foreach(p LIB BIN INCLUDE CMAKE)
set(var INSTALL_${p}_DIR)
if(NOT IS_ABSOLUTE "${${var}}")
set(${var} "${CMAKE_INSTALL_PREFIX}/${${var}}")
endif()
endforeach()
export(TARGETS Core Networking FILE "${PROJECT_BINARY_DIR}/ltkTargets.cmake")
export(PACKAGE ltk)
file(RELATIVE_PATH REL_INCLUDE_DIR "${INSTALL_CMAKE_DIR}" "${INSTALL_INCLUDE_DIR}")
set(CONF_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}")
configure_file(ltkConfig.cmake.in "${PROJECT_BINARY_DIR}/ltkConfig.cmake" #ONLY)
set(CONF_INCLUDE_DIRS "\${LTK_CMAKE_DIR}/${REL_INCLUDE_DIR}")
configure_file(ltkConfig.cmake.in "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/ltkConfig.cmake" #ONLY)
configure_file(ltkConfigVersion.cmake.in "${PROJECT_BINARY_DIR}/ltkConfigVersion.cmake" #ONLY)
install(FILES
"${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/ltkConfig.cmake"
"${PROJECT_BINARY_DIR}/ltkConfigVersion.cmake"
DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/cmake/${project}" )
install(EXPORT ltkTargets DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/cmake/${project}" )
And the Core/CMakeLists.txt contains this:
set (component Core)
add_library(${component} SHARED core.cc )
add_library(${project}::${component} ALIAS ${component})
set_target_properties(${component} PROPERTIES PUBLIC_HEADER "core.h")
install(TARGETS ${component}
EXPORT ltkTargets
COMPONENT ${component}
LIBRARY DESTINATION lib/ltk
ARCHIVE DESTINATION lib/ltk
RUNTIME DESTINATION bin
PUBLIC_HEADER DESTINATION include/ltk/
)
The Networking/CMakeLists.txt is similar to Core/CMakeLists.txt
File ltkConfig.cmake.in contains this:
get_filename_component(LTK_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
set(LTK_INCLUDE_DIRS "#CONF_INCLUDE_DIRS#")
if(NOT TARGET Core AND NOT LTK_BINARY_DIR)
include("${LTK_CMAKE_DIR}/ltkTargets.cmake")
endif()
if(NOT TARGET Networking AND NOT LTK_BINARY_DIR)
include("${LTK_CMAKE_DIR}/ltkTargets.cmake")
endif()
set(LTK_LIBRARIES Core Networking)
And the ltkConfigVersion.cmake.in:
set(PACKAGE_VERSION "#LTK_VERSION#")
# Check whether the requested PACKAGE_FIND_VERSION is compatible
if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
After install this project I see some files in /usr/lib/cmake/ltk:
ltkTargets-noconfig.cmake
ltkTargets.cmake
ltkConfigVersion.cmake
ltkConfig.cmake
Testing
But now when I try to configure some testing project, which has following CMakeLists.txt:
cmake_minimum_required(VERSION 3.21)
project(test)
find_package(ltk COMPONENTS Core REQUIRED)
add_executable(main main.cc)
target_link_libraries(main ltk::Core)
It shows following errors:
-- Configuring done
CMake Error at CMakeLists.txt:4 (add_executable):
Target "main" links to target "ltk::Core" but the target was not found.
Perhaps a find_package() call is missing for an IMPORTED target, or an
ALIAS target is missing?
-- Generating done
I've read following links, but this doesn't helped me:
https://gitlab.kitware.com/cmake/community/-/wikis/doc/tutorials/How-to-create-a-ProjectConfig.cmake-file
How to configure project with COMPONENTS in cmake
Edit
Now I added NAMESPACE ltk:: to install(EXPORT) in ltk/CMakeLists.txt:
install(EXPORT ltkTargets DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/cmake/${project}" NAMESPACE ltk::)
But the testing project which contains #include <core.h> complains:
fatal error: core.h: No such file or directory
Path to /usr/include/ltk has not been exported.
Edit 2
Fixed include problems with:
Added to {Core, Networking}/CMakeLists.txt
target_include_directories(${component} PUBLIC $<INSTALL_INTERFACE:include/ltk>)
Now I can use in my testing project this:
target_include_directories(main PUBLIC ltk)
I have a CMake project with two submodules A and B. B depends on A. In submodule B I would like to search for A using find_package(A CONFIG). My minimal (not) working example would be:
CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
project(AB)
add_subdirectory(B)
add_subdirectory(A)
A/CMakeLists.txt:
message(STATUS "CMake: A")
add_library(A SHARED A.hpp A.cpp)
target_include_directories(A PUBLIC "${CURRENT_SOURCE_DIR}")
install(TARGETS A EXPORT AA LIBRARY DESTINATION lib/)
export(TARGETS A NAMESPACE AA:: FILE ${CMAKE_BINARY_DIR}/A/AConfig.cmake)
export(PACKAGE AA)
A/A.hpp (some non-sense code)
A/A.cpp
B/CMakeLists.txt
find_package(A CONFIG)
message(STATUS "---> ${A_FOUND}")
add_library(B B.hpp B.cpp)
target_link_libraries(B AA::A)
B/B.hpp (some non-sense code)
B/B.cpp
A/CMakeList.txt correctly produces a AConfig.cmake. But as I understand it does that after(!) find_package(A CONFIG) is called and therefore AConfig.cmake is not found.
Any idea how to force find_package() to run after A is executed?
Of course I know that in this example find_package does not make any sense. In my actual project the submodules are external software which I do not want to modify (in my case parallelSTL and TBB).
Indeed cmake currently does not properly support this. A discussion for future plans can be found here. Here is my ugly workaround:
A/CMakeLists.txt:
message(STATUS "CMake: A")
add_library(A SHARED A.hpp A.cpp)
target_include_directories(A PUBLIC "${CURRENT_SOURCE_DIR}")
install(TARGETS A EXPORT AA LIBRARY DESTINATION lib/)
export(TARGETS A NAMESPACE AA:: FILE ${CMAKE_BINARY_DIR}/A/AConfig.cmake)
export(PACKAGE AA)
add_library(AA::A ALIAS A)
file(WRITE ${CMAKE_BINARY_DIR}/A/AConfig.cmake "")
# include(CMakePackageConfigHelpers)
# write_basic_package_version_file(
# ${CMAKE_BINARY_DIR}/A/AConfigVersion.cmake
# VERSION 1.0.0
# COMPATIBILITY AnyNewerVersion)
So the idea is to create a dummy file AConfig.cmake. This is overwritten later but it ensures that find_package() does not fail before it is actually created. Then we need to alias the target A to the name after the import AA::A. In case that there is a requirement on the version of A, than a file AConfigVersion.cmake needs to be created as well.
I created a MWE on github.
I have two C++ projects using CMake 3.12.2.
The first one is MODULE library (a dynamically loaded plugin). It installs a DLL, a header file and the configuration file for CMake.
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
project(MyPlugin LANGUAGES CXX)
set(CMAKE_DEBUG_POSTFIX "d")
# MODULE libraries are dynamically loaded at runtime and never linked against
add_library(MyPlugin MODULE
include/a.h
src/a.cpp
src/b.h
src/b.cpp
)
target_include_directories(MyPlugin
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include/MyPlugin>
PRIVATE
src
)
install(TARGETS MyPlugin EXPORT MyPluginConfig
# MODULE libraries are installed as LIBRARY
LIBRARY DESTINATION plugins COMPONENT Runtime
RUNTIME DESTINATION bin COMPONENT Runtime
PUBLIC_HEADER DESTINATION include/MyPlugin COMPONENT Development
)
install(FILES $<TARGET_PDB_FILE:MyPlugin> DESTINATION plugins OPTIONAL COMPONENT Runtime)
install(
DIRECTORY include/
DESTINATION include/MyPlugin
FILES_MATCHING PATTERN "*.h"
)
install(EXPORT MyPluginConfig
NAMESPACE MyPlugin::
DESTINATION lib/cmake/MyPlugin
)
The second one is a simple executable which pulls in the header file of the plugin via target_link_libraries (the modern CMake way).
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
project(MyExe LANGUAGES CXX)
set(CMAKE_DEBUG_POSTFIX "d")
find_package(MyPlugin REQUIRED)
add_executable(MyExe src/main.cpp)
target_link_libraries(MyExe MyPlugin::MyPlugin)
Using the vs2015 generated solution the link fails because the plugin DLL is fed during the link of the executable...
Has anyone a solution for this or should I file a bug?
Regards.
A solution to this issue is to split the library in two: an interface library which only provides the headers and a module library which does not export any header.
The module library CMakeLists.txt becomes:
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
project(MyPlugin LANGUAGES CXX)
set(CMAKE_DEBUG_POSTFIX "d")
# Move public headers to a dedicated INTERFACE library
add_library(MyPluginInterface INTERFACE)
add_custom_target(Includes SOURCES include/a.h)
target_include_directories(MyPluginInterfacecmake_minimum_required(VERSION 3.12 FATAL_ERROR)
project(MyPlugin LANGUAGES CXX)
set(CMAKE_DEBUG_POSTFIX "d")
# Move public headers to a dedicated INTERFACE library
add_library(MyPluginInterface INTERFACE)
add_custom_target(Includes SOURCES include/a.h)
target_include_directories(MyPluginInterface
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include/MyPlugin>
)
install(TARGETS MyPluginInterface EXPORT MyPluginConfig
PUBLIC_HEADER DESTINATION include/MyPlugin COMPONENT Development
)
# MODULE libraries are dynamically loaded at runtime and never linked against
add_library(MyPlugin MODULE
src/a.cpp
src/b.h
src/b.cpp
)
target_link_libraries(MyPlugin MyPluginInterface)
install(TARGETS MyPlugin
# MODULE libraries are installed as LIBRARY
LIBRARY DESTINATION plugins COMPONENT Runtime
RUNTIME DESTINATION bin COMPONENT Runtime
)
install(FILES $<TARGET_PDB_FILE:MyPlugin> DESTINATION plugins OPTIONAL COMPONENT Runtime)
install(
DIRECTORY include/
DESTINATION include/MyPlugin
FILES_MATCHING PATTERN "*.h"
)
install(EXPORT MyPluginConfig
NAMESPACE MyPlugin::
DESTINATION lib/cmake/MyPlugin
)
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include/MyPlugin>
)
install(TARGETS MyPluginInterface EXPORT MyPluginConfig
PUBLIC_HEADER DESTINATION include/MyPlugin COMPONENT Development
)
# MODULE libraries are dynamically loaded at runtime and never linked against
add_library(MyPlugin MODULE
src/a.cpp
src/b.h
src/b.cpp
)
target_link_libraries(MyPlugin MyPluginInterface)
install(TARGETS MyPlugin
# MODULE libraries are installed as LIBRARY
LIBRARY DESTINATION plugins COMPONENT Runtime
RUNTIME DESTINATION bin COMPONENT Runtime
)
install(FILES $<TARGET_PDB_FILE:MyPlugin> DESTINATION plugins OPTIONAL COMPONENT Runtime)
install(
DIRECTORY include/
DESTINATION include/MyPlugin
FILES_MATCHING PATTERN "*.h"
)
install(EXPORT MyPluginConfig
NAMESPACE MyPlugin::
DESTINATION lib/cmake/MyPlugin
)
The executable CMakeLists.txt becomes:
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
project(MyExe LANGUAGES CXX)
set(CMAKE_DEBUG_POSTFIX "d")
find_package(MyPlugin REQUIRED)
add_executable(MyExe src/main.cpp)
target_link_libraries(MyExe MyPlugin::MyPluginInterface)