How do I Install bundled interface dependencies with modern CMake? - c++

What is the proper way to install bundled interface dependencies in Modern CMake?
I have a library MyLib that has an interface dependency on libDep (MyLib.hpp contains #include <libDep.h>). Anything that depends on MyLib also transitively depends on libDep.
libDep is a single header taken from a gist so I have included it part of the source-tree of MyLib
$ tree
.
├── CMake
│   ├── MyLibConfig.cmake.in
│   └── modules
│   └── FindlibDep.cmake
├── CMakeLists.txt
├── include
│   └── MyLib
│   └── MyLib.hpp
├── src
│   └── MyLib.cpp
└── third_party
└── libDep
└── libDep.h
I would like to install libDep with myLib, in with a path like include/MyLib/third_party/libDep.
Here is the CMakeLists for MyLib
cmake_minimum_required(VERSION 3.3.0)
Project(MyLib
DESCRIPTION "Library with bundled interface dependency"
LANGUAGES CXX)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/CMake/modules)
# Find LibDep dependency
find_package(libDep REQUIRED)
# MyLib library
add_library(MyLib STATIC
${CMAKE_SOURCE_DIR}/src/MyLib.cpp)
target_include_directories(MyLib
PUBLIC $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include/MyLib>
PUBLIC $<INSTALL_INTERFACE:include/MyLib>)
target_link_libraries(MyLib
INTERFACE libDep)
MyLib locates libDep with FindlibDep.cmake located in CMAKE_MODULE_PATH
find_path(LibDep_INCLUDE_DIR
NAMES LibDep.hpp
PATHS third_party/libDep)
PATH_SUFFIXES Mylib/third_party/libDep)
mark_as_advanced(LibDep_FOUND LibDep_INCLUDE_DIR)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LibDep
REQUIRED_VARS
LibDep_INCLUDE_DIR
)
if(LibDep_FOUND)
set(LibDep_INCLUDE_DIRS ${LibDep_INCLUDE_DIR})
endif()
if(LibDep_FOUND AND NOT TARGET MyLib::LibDep)
add_library(MyLib::LibDep INTERFACE IMPORTED)
set_target_properties(MyLib::LibDep PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${LibDep_INCLUDE_DIR})
endif()
I install MyLib like
install(TARGETS MyLib
EXPORT MyLibTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(MyLibConfigVersion.cmake)
install(EXPORT MyLibTargets
FILE MyLibTargets.cmake
NAMESPACE MyLib::
DESTINATION lib/cmake/MyLib)
configure_file(CMake/MyLibConfig.cmake.in MyLibConfig.cmake #ONLY)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
DESTINATION lib/cmake/MyLib)
install(DIRECTORY ${MyLib_PUBLIC_INCLUDE_DIR}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
# Package with CPack
include(InstallRequiredSystemLibraries)
include(CPack)
MyLibConfig.cmake.in declares a dependency on libDep
include(CMakeFindDependencyMacro)
# Dependencies
add_library(libDep REQUIRED)
# Add the targets file
include("${CMAKE_CURRENT_LIST_DIR}/MyLibTargets.cmake")
libDep is installed into MyLib's tree with
install(DIRECTORY ${libDep_INCLUDE_DIRS}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/MyLib/third_party)
and I copy the find module with
install(FILES
${CMAKE_MODULE_PATH}/FindlibDep.cmake
DESTINATION lib/cmake/MyLib)
Unfortunately users of MyLib don't see FindlibDep.cmake by default
$ cmake ..
CMake Error at /usr/share/cmake-3.10/Modules/CMakeFindDependencyMacro.cmake:48 (find_package):
By not providing "FindlibDep.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "libDep", but
CMake did not find one.
Could not find a package configuration file provided by "libDep" with any
of the following names:
libDepConfig.cmake
libDep-config.cmake
Add the installation prefix of "libDep" to CMAKE_PREFIX_PATH or set
"libDep_DIR" to a directory containing one of the above files. If "libDep"
provides a separate development package or SDK, be sure it has been
installed.
Call Stack (most recent call first):
/usr/local/lib/cmake/MyLib/MyLibConfig.cmake:5 (find_dependency)
CMakeLists.txt:9 (find_package)
-- Configuring incomplete, errors occurred!
The user can manually locate FindlibDep.cmake and add it to their CMAKE_MODULE_PATH but that shouldn't be required.
A reproduction of this issue is available on GitHub.

You need to use the PROJECT_ variable for the source dir instead of the CMAKE_ variable:
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMake/modules)
PROJECT_SOURCE_DIR gets the source dir from the most recently executed project() directive (in this case, your library).
You should also be adding your current CMAKE_MODULE_PATH to the new path, as shown above.

Related

Build of multiple subprojects with library that has external dependency

I'm struggling with an project that includes CMake (version 3.11) and subdirectories. It includes ITK as an external library.
I want to create library that I can use in the different subdirectories like MyApp to build multiple applications that are based on MyLib and every change in MyLib is updated in the other applications. The MyLib is a header-only templated library. In addition this kind of libraries do not get a own project in VisualStudio.
My questions are:
What is the correct way of using CMake in this scenario? I have searched and tried some example but was not successful. Ideally I can build applications depending on the MyLib independently. The top CMakeLists.txt may not pull in the includes from the library. This should be done by the applications.
What would change if the MyLib becomes a non-header-only library? In the future I may add non templated classes.
Bonus question:
How can I add the header-only library to VisualStudio? This is more a question for convencience.
Any help is appreciated and I can provide more details if necessary.
The source environment looks like this:
ProjectDirectory
- CMakeLists.txt
-- MyLib
- CMakeLists.txt
-- include
- File1.h (template header)
- File1.hxx (template body)
-- src
- (empty for now)
-- MyApp
- CMakeLists.txt
- AppFile1.cxx (File containing main function)
This is an out-of-source-build project. Ideally the compiled libraries and application are put in a directory like this (on Windows):
└── bin
├── Debug
│ ├── MyApp.exe
│ └── MyLib.lib
└── Release
├── MyApp.exe
└── MyLib.lib
ProjectDirectory CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(project_name)
# Setup build locations.
if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
endif()
if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
endif()
if(NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
endif()
add_subdirectory(MyLib)
add_subdirectory(MyApp)
MyLib CMakeLists.txt:
find_package(ITK REQUIRED
COMPONENTS RTK ITKImageIO)
include(${ITK_USE_FILE})
set(HDRS
${CMAKE_CURRENT_SOURCE_DIR}/include/File1.h
${CMAKE_CURRENT_SOURCE_DIR}/include/File1.hxx
)
add_library(MyLib ${HDRS})
target_include_directories(MyLib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
src)
target_link_libraries(MyLib
PUBLIC ${ITK_LIBRARIES})
export(TARGETS MyLib FILE MyLibConfig.cmake)
MyApp CMakeLists.txt
find_package(ITK REQUIRED
COMPONENTS RTK ITKRegistrationMethodsv4 ITKTransformIO ITKImageIO)
include(${ITK_USE_FILE})
set(SRCS
AppFile1.cxx)
add_executable(MyApp ${SRCS})
include_directories(${CMAKE_SOURCE_DIR}/MyLib/include)
target_link_libraries(MyApp
${ITK_LIBRARIES}
MyLib)
Edit1: Remove the project(X) from MyLib and MyApp CMakeLists.txt and change target to MyLib.
You can have an empty project, just create a custom target:
add_custom_target(MyLib SOURCES ${CMAKE_MYLIB_SRC} ${CMAKE_MYLIB_HDR})
As your code is header only, any depend target will see the changes in the files and would be recompiled. There is nothing to do on top of this. You don't have to create the target, although for your third question, that's the answer.
But if ITK is only your dependency, now that you have a target, you can add PUBLIC properties on it, like dependent libraries that need to be linked against it because of your library.
In that case, the code for them needs to add:
target_link_library(${NEWTARGET} PRIVATE MyLib) # PUBLIC/PRIVATE has to be tailored
That's the answer for question 1.
If your library becomes non-header only, just change the add_custom_target call.

cmake the following imported targets are referenced, but are missing

I have a repository with two libraries (liba and libb) whereas liba
depends on libb. They are part of a single repository and are built using a single cmake "context". The file structure is shown below:
├── CMakeLists.txt
├── liba
│ ├── CMakeLists.txt
│ ├── internal
│ │ └── private.hh
│ ├── module.cc
│ ├── module.hh
└── libb
├── CMakeLists.txt
├── other.cc
└── other.hh
Everything compiles and installs without any issues. Although, if I try to create a new project that depends on liba. Like so:
cmake_minimum_required(VERSION 3.5)
find_package(Threads REQUIRED)
find_package(OpenCV REQUIRED)
find_package(liba REQUIRED)
add_executable(exec exec.cc)
target_link_libraries(exec PRIVATE is::liba)
I get the following error:
CMake Error at CMakeLists.txt:5 (find_package):
Found package configuration file:
/home/hodor/is-sdk/lib/cmake/liba/libaConfig.cmake
but it set liba_FOUND to FALSE so package "liba" is considered to be NOT
FOUND. Reason given by package:
The following imported targets are referenced, but are missing: is::libb
What I am missing here? Repository link for context
CMakeLists that generates liba:
cmake_minimum_required(VERSION 3.5)
include(GNUInstallDirs)
find_package(OpenCV REQUIRED core)
find_package(Threads REQUIRED)
list(APPEND liba_public_headers
"module.hh"
)
list(APPEND liba_private_headers
"internal/private.hh"
)
list(APPEND liba_sources
"module.cc"
${liba_public_headers}
${liba_private_headers}
)
add_library(liba ${liba_sources})
target_include_directories(
liba
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}> # for headers when building
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}> # for generated files in build mode
$<INSTALL_INTERFACE:include/is/liba> # for clients in install mode
PRIVATE
${OpenCV_INCLUDE_DIRS}
)
target_link_libraries(liba
PRIVATE
is::libb
${OpenCV_LIBRARIES}
Threads::Threads
)
set_property(TARGET liba PROPERTY CXX_STANDARD 11)
install(
TARGETS liba EXPORT libaTargets
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
install(FILES ${liba_public_headers} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/is/liba)
install(
EXPORT libaTargets
FILE libaConfig.cmake
NAMESPACE is::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/liba
)
CMakeLists that generates libb:
cmake_minimum_required(VERSION 3.5)
include(GNUInstallDirs)
list(APPEND libb_public_headers
"other.hh"
)
list(APPEND libb_sources
"other.cc"
${libb_public_headers}
)
add_library(libb ${libb_sources})
target_include_directories(
libb
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}> # for headers when building
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}> # for generated files in build mode
$<INSTALL_INTERFACE:include/is/libb> # for clients in install mode
)
install(
TARGETS libb EXPORT libbTargets
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
install(FILES ${libb_public_headers} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/is/libb)
install(
EXPORT libbTargets
FILE libbConfig.cmake
NAMESPACE is::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libb
)
add_library(is::libb ALIAS libb)
Top level CMakeLists that includes liba and libb:
cmake_minimum_required(VERSION 3.5)
add_subdirectory(liba)
add_subdirectory(libb)
After watching C++Now 2017: Daniel Pfeifer “Effective CMake"
I now realized what was missing in my configuration. As mentioned by #Tsyvarev the problem was about libaConfig.cmake.
When exporting a library with dependencies you should export mylibraryTargets.cmake, like so:
install(
EXPORT libaTargets
FILE libaTargets.cmake
NAMESPACE is::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/liba
)
And then manually (a bit disappointed here I have to say) write a mylibraryConfig.cmake with all the dependencies like so:
include(CMakeFindDependencyMacro)
find_dependency(libb)
find_dependency(OpenCV)
find_dependency(Threads)
include("${CMAKE_CURRENT_LIST_DIR}/libaTargets.cmake")

CMakeLists.txt for third-party C files within C++ project

My C++ project doggo has a doggo/external/ directory for third-party code. Currently it contains gtest and a CMakeLists.txt:
# Google gtest for unit testing.
add_subdirectory(gtest)
message("gtest include dir: ${gtest_SOURCE_DIR}")
include_directories(${gtest_SOURCE_DIR})
My top-level doggo/CMakeLists.txt contains the line add_subdirectory(external) to find and build the third-party libraries. Everything works like a charm -- I can include gtest with #include <gtest/gtest.h>. Now I'd like to add the randomkit C library to doggo/external/, as is done here: randomkit from numpy.
How can I get randomkit to build in my doggo/external/ dir? What should the doggo/external/CMakeLists.txt look like?
I should then be able to include the C headers for use in my x.cpp files by including the headers inside an extern "C" { ... } block (details here).
UPDATE: How do I install randomkit here?
I've included a CMakeLists.txt entry like that above but for randomkit, and the directory looks like,
external
├── CMakeLists.txt
├── gtest
│  └── ...
└── randomkit
├── CMakeLists.txt
├── distributions.c
├── distributions.h
├── randomkit.c
└── randomkit.h
and the randomkit/CMakeLists.txt:
project(randomkit)
file(GLOB SOURCES "*.c")
add_library(randomkit SHARED ${SOURCES})
INSTALL(
DIRECTORY ${CMAKE_SOURCE_DIR}/
DESTINATION "/usr/local/"
#DESTINATION ""
FILES_MATCHING PATTERN "*.h*")
(second DESTINATION commented out to show I tried that as well)
Yet when I run the build steps for my top-level project doggo I get an error trying to #include <randomkit/distributions.h>:
doggo/src/random_fooz.cpp:10:37: fatal error: randomkit/distributions.h: No such file or directory
UPDATE 2: doggo/CMakeLists.txt:
project(doggo)
# Find and build third-party libraries
add_subdirectory(external)
# Add source dirs to the search path so cmake can find headers
include_directories(${CMAKE_SOURCE_DIR}/include/)
# Collect source files and build
file(GLOB_RECURSE doggo_srcs ${CMAKE_SOURCE_DIR}/src/*.cpp)
add_library(doggo ${doggo_srcs})
# Setup executables
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin/)
add_subdirectory(exec)
# Tests
add_subdirectory(test)
In the randomkit/CMakeLists.txt write:
project(randomkit)
file(GLOB SOURCES "*.c")
add_library(randomkit SHARED ${SOURCES})
target_include_directories(randomkit PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
INSTALL(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/
DESTINATION "include" # this a the subdirectory with ${CMAKE_INSTALL_PREFIX}
FILES_MATCHING PATTERN "*.h*")
In the main CMakeLists.txt, you do:
add_library(doggo ${doggo_srcs})
target_link_libraries(doggo PUBLIC randomkit)
target_include_directories(doggo PUBLIC ${CMAKE_SOURCE_DIR}/include/)
Don’t use include_directories.
Now, because the randomkit target has the PUBLIC property with the right include directories, those include directories will be automatically used when building the doggo library. And again, because the doggo library has include directories and libraries in its public interface, executables that you link to doggo will automatically be linked to these libraries, and find their include files.
Note that the INSTALL command in randomkit/CMakeLists.txt is only executed when you actually run the install target. When building, the include files must be found in the source tree.

CPack: Interface include directories are missing in archive

I am using CMake 3.10.1 and trying to use CPack to generate archives for a library and I cannot get it to add the interface include directory to the archive.
The library and the generated export files are added as expected, however the include directory (added using target_include_directories(... PUBLIC ...) is missing entirely.
The CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(Test VERSION 1.0.0 LANGUAGES CXX)
add_library(${PROJECT_NAME} SHARED foo.cpp) #add sources and executable
target_include_directories(${PROJECT_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc>
$<INSTALL_INTERFACE:inc>
)
install(TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}
INCLUDES DESTINATION inc
PUBLIC_HEADER DESTINATION inc
LIBRARY DESTINATION lib
)
install(EXPORT ${PROJECT_NAME} DESTINATION .)
include(CPack)
The contents of my source dir:
├── CMakeLists.txt
├── foo.cpp
└── inc
└── foo.h
The contents of the tgz generated by cpack -G TGZ .
├── lib
│   └── libTest.so
├── Test.cmake
└── Test-noconfig.cmake
Any ideas why it could be missing the inc directory?
Generator-like expression $<INSTALL_INTERFACE> used in the target_include_directories() command by itself doesn't install corresponded directory. You need to install this directory manually (with install(FILES) or install(DIRECTORY)).
Expression $<INSTALL_INTERFACE> specifies interface include directory for the target in the config file, which exports install tree (see install(EXPORT) command).
Expression $<BUILD_INTERFACE> specified interface include directory for the target in the project itself, and in the config file which exports build tree (see EXPORT() command).
But these expressions doesn't enforce $<BUILD_INTERFACE> directory to be copied into $<INSTALL_INTERFACE> one on installation. As opposite, content of this directories usually differs: aside from header files for outer use, installed into $<INSTALL_INTERFACE> directory, a directory $<BUILD_INTERFACE> may contain header files for project's internal use, which are not installed.

Cmake: Exporting subproject targets to main project

I currently have a project called LIBS with a structure like this:
├── Lib1
│ ├── CMakeLists.txt
│ ├── lib1-class.cpp
│ └── lib1-class.h
├── lib2
│ └── CMakeLists.txt
│ ├── lib2-class.cpp
│ ├── lib2-class.h
├── cmake
│ └── LIBSConfig.cmake.in
├── CMakeLists.txt
in the main cmake file, I have:
install(
TARGETS
lib1
lib2
DESTINATION
${PROJECT_DIRNAME_lib}
EXPORT
${PROJECT_NAME}Exports
)
install(
EXPORT
${PROJECT_NAME}Exports
DESTINATION
${PROJECT_DIRNAME_lib}
)
as I want to export these in a package that is discoverable by find_package().
My problem is that I generate lib1 and lib2 in their respective directories and when installing them, Cmake tells me that
Error:install TARGETS given target "lib1" which does not exist in this directory.
As suggested here, My understanding is that I should use Export() and in lib1 and lib2, have something of the form:
export(TARGETS lib1 FILE lib1Exports.cmake)
and in the LIBS project, have something like this:
ADD_LIBRARY(lib1 UNKNOWN IMPORTED)
set_property(TARGET lib1 PROPERTY IMPORTED_LOCATION lib1)
However it does not like me using the same name for this library that is being added from the parent project. It tells me:
Error:add_library cannot create imported target "lib1" because another target with the same name already exists.
so the library is available and I can link to it, etc. if I were to create another target in the parent directory, but I can't install it.
I have found the exact same problem in a bug report here but I believe cmake handles things differently now and I am just not doing it correctly.
So am I doing it wrong? I would like to avoid using external packages if possible.
Update: the accepted solution works only for cases where there is no dependency between lib1, lib2. In that case one should use the solution provided to this question.
As noted in the bugreport you refer to install() command should be issued from the same directory where target is created. As you have libraries target created in different directories, you need to assign different export names for them, and, consequently, different export files.
But you are free to include both export files into the LIBSConfig.cmake script:
cmake/LIBSConfig.cmake:
get_filename_component(SELF_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
include(${SELF_DIR}/LIBS-lib1.cmake)
include(${SELF_DIR}/LIBS-lib2.cmake)
lib1/CMakeLists.txt:
add_library(lib1 ...)
install(TARGET lib1 EXPORT lib1-export ...)
lib2/CMakeLists.txt:
add_library(lib2 ...)
install(TARGET lib2 EXPORT lib2-export ...)
CMakeLists.txt:
add_subdirectory(lib1)
add_subdirectory(lib2)
install(EXPORT lib1-export FILENAME LIBS-lib1.cmake DESTINATION lib/LIBS)
install(EXPORT lib2-export FILENAME LIBS-lib2.cmake DESTINATION lib/LIBS)
install(FILES cmake/LIBSConfig.cmake DESTINATION lib/LIBS)
Note, that export command exports build tree. It is usually not suitable for find_package, which is normally used for find installed files.