CMake include header-only-library into another library, make fail - c++

I have the following folder structure:
- lib/
-libA/
-include/
- libA.h
-src/
- libA.cpp
-CMakeLists.txt (1)
-libB/
-headerB1.h
-headerB2.h
-headerBx.h
-CMakeLists.txt (2)
- src/
-main.cpp
- CMakeLists.txt (3)
in libA.h I try to include one of the header files from libB. In this case that would be headerB1.h by doing
#include <headerB1.h>
But unfortunately, I get the following error in cmake:
fatal error C1083: cannot open include file: 'headerB1.h': No such file or directory from the libA.h file.
I have the following CMake contents:
/CMakeLists.txt (3)
cmake_minimum_required(VERSION 3.10)
project(project)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(SOURCES
src/main.cpp
)
add_executable(${PROJECT_NAME} ${SOURCES})
add_subdirectory("lib/libA")
add_subdirectory("lib/libB")
target_link_libraries(${PROJECT_NAME} PUBLIC libA)
target_link_libraries(${PROJECT_NAME} PUBLIC libB)
lib/libA/CMakeLists.txt (1)
add_library(libA STATIC)
target_sources(libA PRIVATE src/libA.cpp)
target_include_directories(libA
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
)
lib/libB/CMakeLists.txt (2)
add_library(libB INTERFACE)
target_include_directories(libB INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
library B (libB) is a header only library in my view (correct me if I'm using the incorrect terms).
So libA actually 'uses' libB but somehow, adding or creating libB is not executed as I thought it would. What are my misconceptions here and how can I solve it?
main.cpp:
#include <libA.h> //This works!
int main(void) {
//do something with libA class / functions
}
libA.h:
#include <headerB1.h> //This causes the cmake (mingw32-make.exe) error
I use:
cmake
vscode
mingw32-make
I actually did take a look at: CMake include library in another library
But making my CMakeLists (1) like the following code did not work for me unfortunately. Or, again, I have misunderstood the solution.
add_library(libA SHARED)
target_sources(libA PRIVATE src/libA.cpp)
target_include_directories(libA
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
)
target_link_libraries(libA PRIVATE libB)
target_include_directories(libA PUBLIC ${CMAKE_ROOT}/lib/libB)

Ok, this was actually pretty straightforward for a solution but it does make sense that libB would have to be compiled first because libA makes use of it.
So, in CMakeLists.txt (3) I have switched the two following lines:
add_subdirectory("lib/libA")
add_subdirectory("lib/libB")
to
add_subdirectory("lib/libB")
add_subdirectory("lib/libA")
And I also made sure to have added the following code snippet like suggested in the other stackoverflow question, to the libA CMakeLists.txt (1). CMake include library in another library
target_link_libraries(libA PRIVATE libB)
target_include_directories(libA PUBLIC ${CMAKE_ROOT}/lib/libB)
Now, all compiles as expected without errors.

Related

Why CMake does not propagate the PUBLIC include directories between libraries?

I have a C++ project with three shared libraries, let's call them libA, libB and libC. libA is dependant to both libB and libC. All of these three libraries are located inside a folder called utilities. Here is what I have:
Root CMakeLists.txt file:
cmake_minimum_required (VERSION 3.20) # Required because of policy CMP0074
project(Test VERSION 2022)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
file(TO_CMAKE_PATH $ENV{SHARED_PATH} SHARED_PATH)
add_subdirectory (utilities)
for libB I have the CMakeLists.txt like this:
add_library(libB SHARED
inc/libB.h
inc/libBExport.h
src/libB.cpp
)
target_include_directories(libB
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/inc
PUBLIC ${SHARED_PATH}/inc
)
target_compile_definitions(libB
PRIVATE LIBB_DLL)
and the same goes for libC:
add_library(libC SHARED
inc/libC.h
inc/libCExport.h
src/libC.cpp
)
target_include_directories(libC
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/inc
PUBLIC ${SHARED_PATH}/inc
)
target_compile_definitions(libC
PRIVATE LIBC_DLL)
Now, the CMakeLists.txt file for libA is like this:
add_library(libA SHARED
inc/libA.h
inc/libAExport.h
src/libA.cpp
)
target_include_directories(libA
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/inc
PUBLIC ${SHARED_PATH}/inc
)
target_link_directories(libA
PUBLIC libB
PUBLIC libC
)
target_compile_definitions(libA
PRIVATE LIBA_DLL)
Now, I generate the Visual Studio solution file using cmake:
> cmake -G "Visual Studio 16 2019" -A x64
Everything goes fine. But when opening the solution with VS2019, libB and libC are compiling fine, but libA cannot be compiled with this error:
D:\code\proj\utilities\inc\libA.h(5,10): fatal error C1083: Cannot open include file: 'libB.h': No such file or directory
D:\code\proj\utilities\inc\libA.h(9,10): fatal error C1083: Cannot open include file: 'libC.h': No such file or directory
Can someone explain me why the the PUBLIC include directories of libB and libC are not inherited to libA?
Instead of the following:
target_link_directories(libA
PUBLIC libB
PUBLIC libC
)
You should use target_link_libraries().
target_link_libraries(libA
PUBLIC libB
PUBLIC libC
)
This will setup a dependency between the libraries which sets the include path, compiler settings, additional link directories, and additional link libraries.

Adding library dependencies to interface libraries in cmake

I have the following cmake file
cmake_minimum_required(VERSION 3.16)
find_package(fmt)
add_library(mylib INTERFACE )
add_dependencies(mylib fmt::fmt-header-only)
target_compile_features(mylib INTERFACE cxx_std_20)
target_include_directories(mylib INTERFACE .)
add_executable(test_exe test_exe.cpp)
target_link_libraries(test_exe PUBLIC mylib)
But fmt is not linked against test_exe unless I explicitly add it to the dependencies. Am I defining the dependencies of mylib wrong?
The error is the following and it goes away if I add fmt::header-only to the link libraries of test_exe
fatal error: 'fmt/format.h' file not found
#include <fmt/format.h>
^~~~~~~~~~~~~~
add_dependencies(mylib fmt::fmt-header-only)
simply makes sure that the target fmt::fmt-header-only is up to date before mylib is built. It doesn't link fmt::fmt-header-only regardless of the target type of mylib. Linking is done via target_link_libraries
target_link_libraries(mylib INTERFACE fmt::fmt-header-only)

CMake Include paths - library that depend on external library

Take for example this project structure
project
CMakeLists.txt
main.cpp
libA
src.cpp
CMakeLists.txt
libB
foo.h
foo.cpp
CMakeLists.txt
src.cpp and main.cpp:
#include "foo.h"
.
.
.
I need both src.cpp and main.cpp to have libB in their include path, what I've tried to do is:
libB/CMakeLists.txt:
add_library(libB SHARED
src.cpp)
libA/CMakeLists.txt:
add_library(libA SHARED
foo.cpp)
target_link_libraries(libA libB)
project/CMakeLists.txt:
add_subdirectory(libA)
add_subdirectory(libB)
add_executable(App main.cpp)
target_include_directories(App PUBLIC libB)
target_link_libraries(App libA libB)
And yet I get an error that
src.cpp: fatal error: foo.h: No such file or directory
In the top-level:
cmake_minimum_required(VERSION 3.21)
project(project)
option(BUILD_SHARED_LIBS "Build shared libs instead of static" ON)
add_subdirectory(libA)
add_subdirectory(libB)
add_executable(App main.cpp)
target_link_libraries(App PRIVATE libA libB)
In libA:
add_library(libA src.cpp)
# Use PUBLIC below if libA exposes libB's types in its API.
target_link_libraries(libA PRIVATE libB)
In libB:
add_library(libB foo.cpp)
target_include_directories(
libB PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
)
PUBLIC and INTERFACE properties on targets propagate to their linkees. Thus, when we set up the include directories on libB, any target linking to it will get its source directory added it its include path.
We guard the include directory with $<BUILD_INTERFACE:...> so that if you ever install() the library somewhere it might be re-used in another CMake build, it won't remember details of your specific file system.

CMake: Unable to include external library header files in my library's header files that are not included in any source files

I've tried looking for an answer to this question. There are similar questions but none that answer mine.
I am trying to create a library that depends on a couple other external libraries. I have the external libraries as git submodules and compile them myself in CMake using add_subdirectory followed by target_link_library.
After doing that, I am able to use the external libraries and include their header files in any source file in my library that I have added using target_sources and also in any header file that at least one of these source files include.
For example, I have an external library glm and call target_sources(myLib PRIVATE Application.cpp) in my library. The header files in my library are added to the project using target_include_directories.
Now, I can do #include <glm/glm.hpp> in the Application.cpp file with no problems.
I also have an Application.hpp file that is included in the Application.cpp file. Because it is included, that allows me to do #include <glm/glm.hpp> in the Application.hpp file as well.
However, now I also have a Core.hpp file without a corresponding Core.cpp file. Because the Core.hpp file is not included in any source files, doing #include <glm/glm.hpp> throws an error that the file cannot be found.
This is my root CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
project(Ivory)
set(CMAKE_CXX_STANDARD 20)
add_library(${PROJECT_NAME} SHARED)
# Determine compiler platform (x32 or x64)
math(EXPR PLATFORM_BITS "8*${CMAKE_SIZEOF_VOID_P}")
set(PLATFORM "x${PLATFORM_BITS}")
# Determine target build platform
target_compile_definitions(${PROJECT_NAME}
PUBLIC
IVORY_WINDOWS
PRIVATE
IVORY_BUILD_DLL
)
# Set output directory
set(OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin/${CMAKE_SYSTEM_NAME}-${CMAKE_BUILD_TYPE}-${PLATFORM})
# Subdirectories
add_subdirectory(src)
add_subdirectory(tests)
add_subdirectory(executables)
target_include_directories(${PROJECT_NAME} PUBLIC include)
# External Libraries #
set(BUILD_SHARED_LIBS OFF)
# GLM
add_subdirectory(lib/glm)
target_link_libraries(${PROJECT_NAME} PRIVATE glm)
# GLEW
add_subdirectory(lib/glew)
target_link_libraries(${PROJECT_NAME} PRIVATE libglew_static)
# SFML
add_subdirectory(lib/SFML)
target_link_libraries(${PROJECT_NAME} PRIVATE sfml-window sfml-audio)
# SPDLOG
add_subdirectory(lib/spdlog)
target_link_libraries(${PROJECT_NAME} PRIVATE spdlog)
# MinGW
target_link_libraries(${PROJECT_NAME} PRIVATE -static)
# Output Location
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIRECTORY})
This is the src CMakeLists.txt:
target_sources(${PROJECT_NAME}
PRIVATE
Application.cpp
)
I am using CLion on Windows 10 with MinGW 7.3 compiler (64 bit).
What am I doing wrong?
If the Core.hpp header is meant to be included by clients and glm headers are called from this header the glm dependency in the target_link_libraries command must be PUBLIC not PRIVATE.
For the CMake documentation see here. For a more detailed explanation of the PUBLIC, PRIVATE, and INTERFACE keywords with examples see here.

Linking two libraries together with CMake

File Structure:
CMakeLists.txt
src/
evolution.cpp
evolution.h
main.cpp
helpers/
disposable.h
engine/
game.h
game.cpp
CMakeLists.txt:
cmake_minimum_required(VERSION 3.17)
project(evolution)
add_library(helpers
src/helpers/disposable.h)
set_target_properties(helpers PROPERTIES LINKER_LANGUAGE CXX)
add_library(engine
src/engine/game.h
src/engine/game.cpp)
add_executable(evolution src/main.cpp)
target_link_libraries(evolution engine helpers)
The Game class (game.h) inherits from Diposable (disposable.h), but I am unable to build the project with a simple #include "disposable.h" in the game.h file.
How do I configure CMake so that the engine library can see the helpers library?
CMake error:
[ 20%] Building CXX object CMakeFiles/engine.dir/src/engine/game.cpp.o
In file included from /home/shane/projects/evolution/src/engine/game.cpp:1:
/home/shane/projects/evolution/src/engine/game.h:4:10: fatal error: disposable.h: No such file or directory
4 | #include "disposable.h"
| ^~~~~~~~~~~~~~
compilation terminated.
Adding headers to a project really only helps IDEs find the headers. It doesn't change the build instructions. Instead you need to use target_include_directories() with a INTERFACE or PUBLIC section to get any libraries that link to this to add the include directories to itself.
Your helpers library is more of an interface for now. To get this minimum example working do this:
cmake_minimum_required(VERSION 3.17)
project(evolution)
add_library(helpers INTERFACE) # INTERFACE is good for header-only libraries
target_include_directories(helpers INTERFACE src/helpers)
add_library(engine
src/engine/game.h
src/engine/game.cpp)
add_executable(evolution src/main.cpp)
target_link_libraries(evolution engine helpers)
Now include with angular brackets: #include <disposable.h>
If your library becomes more complicated (more than just header files), then replace INTERFACE in add_library with your sources. To make include directories available to the helpers library and any library that links to it, change INTERFACE to PUBLIC in target_include_directories.
cmake_minimum_required(VERSION 3.17)
project(evolution)
add_library(helpers src/helpers/helper.cpp)
target_include_directories(helpers PUBLIC src/helpers)
add_library(engine
src/engine/game.h
src/engine/game.cpp)
add_executable(evolution src/main.cpp)
target_link_libraries(evolution engine helpers)