Cmake: Exporting subproject targets to main project - c++

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.

Related

How to cross link libraries in CMake

In a c++ CMake project I have an executable main and two libraries lib1 and lib2. A function in lib1 needs a function from lib2 and visa versa. Also, lib1 only contains .h files. The main executable will use both libraries. When I try and "make" the project, I get an error:
error: redefinition of ‘void lib1()’.
The file structure looks somewhat like this
/path/to/my/project
├── CMakeLists.txt # Project directory
├── main.cpp
├── Lib1
│ ├── ...files (.h only)...
│ ├── CMakeLists.txt # lib1 cmake
├── Lib2
│ ├── ...source files (.cpp & .h)...
│ ├── CMakeLists.txt # lib2 cmake
The CMakeLists.txt in the Project directory includes the following:
add_executable(${PROJECT_NAME} main.cpp)
add_subdirectory(Lib1)
add_subdirectory(Lib2)
target_link_libraries(${PROJECT_NAME}
lib2
lib1
)
The CMakeLists.txt in the Lib1 directory includes the following:
add_library(lib1 INTERFACE)
target_include_directories(lib1
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(lib1 INTERFACE
lib2
)
The CMakeLists.txt in the Lib2 directory includes the following:
add_library(lib2 ${SOURCES} ${HEADERS}) # SOURCES and HEADERS set in lines above
target_include_directories(lib2
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(lib2
lib1
)
If I had to guess, the issue is it is trying to import lib1 headers twice. Once from lib2 and once in my main executable. How do I link the libraries so that isn't an issue?
I dont think it's anything related to cmake. Although convoluted (I'd do it in another way but hey it's your code) I think you are defining the body of a function in lib1 where it should reside in a cpp file.
Make that function lib1 inline.
inline void lib1() {
...
}
or alternatively defined it in the header and implement it in a body file
//lib1.h
void lib1();
Then
//lib1.cpp
#include "lib1.h"
void lib1() {
...
}

Accessing include directories of a cmake project added as subdirectory

Consider a simple scenario where my cmake project adds a dependency as a subdirectory:
.
├── CMakeLists.txt
├── src
├── include
│
└── externals
└── BAR
├── CMakeLists.txt
├── src
└── include
The main CMakeLists.txt is something like:
cmake_minimum_required(VERSION 3.0)
project(FOO)
add_subdirectory(externals/BAR)
set(SOURCES ${CMAKE_SOURCE_DIR}/src/foo.cpp
${CMAKE_SOURCE_DIR}/include/bar.hpp)
add_library(${PROJECT_NAME} SHARED ${SOURCES})
target_include_directories(${PROJECT_NAME} PUBLIC
${CMAKE_SOURCE_DIR}/include # this works
${BAR_INCLUDE_DIR}) # this does not
The problem is, the include directories of the added project are not accessable from FOO project.
BAR is a huge dependency with its own sub directories. Its root cmake uses INCLUDE_DIRECTORIES command (instead of target_include_directories). It then does some FILE ( GLOB headers and use them when triggering make install
I am aware that there are many questions regarding subdirectories in cmake, however, in my case cannot modify the CMakeLists.txt in the subdirectory. It's a git submodule that gets updated constantly and it's a pain to modify it constantly.
How can I access BAR's include directories (and later its libraries) without changing its cmakes?
P.S. BAR gets compiled properly and shared library appears in the build folder.

How do I Install bundled interface dependencies with modern CMake?

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.

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.