Accessing include directories of a cmake project added as subdirectory - c++

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.

Related

How to setup CMake files and project structure for a C++ library used by others?

I'm writing a C++ library which has the following structure:
my_project
├── include
├── lib
├── src
│ ├── sourceA.cpp
│ └── sourceB.cpp
├── test
│ ├── test.cpp
│ └── CMakeLists.txt
└── CMakeLists.txt
Now I have multiple questions:
How can I layout CMake files in a way so that I can build a test app that uses it from within the library? Here's what I've got so far:
test/CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(supabase VERSION 0.2.0.0 LANGUAGES C CXX)
add_subdirectory(..)
add_executable(test test.cpp)
target_link_libraries(test PRIVATE mylib)
MakeLists.txt
add_library(mylib)
target_include_directories(mylib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include");
But CMake complains that
directory "C:/dev/myproject" is not a subdirectory of "C:/dev/myproject/test"
So I could of course create a top level CMakeLists.txt that contains the project information and builds test.cpp. BUT: Of course don't want other developers to build the test routines when they just want to build the lib. Which leads me to question number..
Is there a standardised way to layout the project structure so that it satisfies the following requirements:
I can build testscripts from within library and use it at the same time
Other devs can just include my top level CMakeLists.txt and build the library without baggage.
What is best practice here?

Integrate thirdpary shared libraries(*.so) into multiple projects using cmake

I have the following layout which contains multiple projects separate directories (It's a CPP project):
Demo
├── Lib
│   ├── CMakeLists.txt
│   ├── inc
│   └── lib
│   ├── crypto_ic.so
│   └── crypto.so
├── Proj1
│   ├── App1
│   └── CMakeLists.txt
└── Proj2
├── App2
└── CMakeLists.txt
I created one separate third-party-lib.bb file to install crypto_ic.so,crypto.so into /usr/lib/ in the target using do_install_append.
All applications are depending on third-party libraries. There are no parent cmake for proj1,proj2 those are independent projects built by *.bb files using yocto.
Now I need to integrate the third party library for all the applications. I created one interface library for the third party headers and using find_package I am able to find the headers and the compilation is working fine.
CMakeLists.txt(Lib)
cmake_minimum_required(VERSION 3.8)
project(crypto VERSION 1.0.0 LANGUAGES CXX)
include(GNUInstallDirs)
find_package(Boost REQUIRED)
add_library(crypto INTERFACE)
target_link_libraries(crypto INTERFACE
Boost::boost
)
target_include_directories(crypto INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/inc/>
$<INSTALL_INTERFACE:include>
)
# Create and Install *-config.cmake,*-version.cmake files
# Install exported targets, i.e. *-targets.cmake
# Install header files
CMakeLists.txt(Proj1)
cmake_minimum_required(VERSION 3.8)
project(App1 VERSION 1.0.0 LANGUAGES CXX)
include(GNUInstallDirs)
find_package(crypto REQUIRED)
add_executable(run Run.cpp)
target_link_libraries(run PUBLIC
crypto
)
But the problem is while linking I am getting a linker error.
Run.cpp: undefined reference to 'Intialize(std::string hashName)'
Initialize function definition is available in the shared library.
Any thoughts on how to fix the issue and how to remove the hardcode path to the library in CMake?.
I am new to CMake and not sure this is a good approach to follow, only reason I created the Interface library is to install the headers in target and find the package. I can modify it if there are some better solutions?
Edit:
I found the issue in the cmake, I didn't link the shared library files (*.so) in the application that's the reason I am getting the linker error. But I am not getting how to link that from the target?

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.