Using cmake for c++ template library - c++

I have a project which uses cmake to build. The project has a number of submodules which build as libraries. The structure looks like this:
src
├── CMakeLists.txt
├── libA
│   ├── CMakeLists.txt
│   ├── include
│   │   └── A
│   │      └── A.h
│   └── src
│      └── A.cpp
│
├── libB
│   ├── CMakeLists.txt
│   ├── include
│   │   └── B
│   │   └── B.h
│   └── src
│      └── B.cpp
│
├── include
│   └── project.h
├── main
│   ├── CMakeLists.txt
│   └── main.cpp
└── other_main
   ├── CMakeLists.txt
   └── main.cpp
Now it turns out that I need to convert module B to be template based rather than a linkable lib; i.e. I want to export just headers from module B.
Currently module B's CMakeLists.txt contains the following:
add_library(B STATIC ${SOURCES})
target_include_directories(B
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
... other include dirs ...
PRIVATE
src)
# B depends on A
target_link_libraries(B A)
export(
TARGETS B
FILE BLibraryConfig.cmake)
What are the minimum changes I need to make to my CMakeLists files (at either module or project scope) to be able to support B as a template library given that B still depends on A and both my main projects make use of A and B?

As another answer says, you could declare B as an interface library. This approach has some limitations, though. For instance, you cannot set custom properties on interface libraries. Also B's headers might not be properly displayed by an IDE, e.g. QtCreator 4.6.1 does not show them in the project tree.
If this is critical for you, there is an alternative. You could have a static library which contains only headers, but you need to manually specify it's linker language.
add_library(B STATIC ${SOURCES})
target_include_directories(B
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
... other include dirs ...
)
# As B does not have any source files, you have to explicitly
# specify the linker language
set_target_properties(B PROPERTIES LINKER_LANGUAGE CXX)
# B depends on A
target_link_libraries(B A)
export(
TARGETS B
FILE BLibraryConfig.cmake)

You can declare B as an interface library to define it as header-only. It will just require slight modifications of target_include_directories and target_link_libraries (define INTERFACE properties instead of public/private)
add_library(B INTERFACE) # no sources
target_include_directories(B INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
# other include dirs ...
)
# B depends on A
target_link_libraries(B INTERFACE A)
export(
TARGETS B
FILE BLibraryConfig.cmake
)

Related

CMake creating libraries that depend each other

my goal is to create libraries like client and generator and use them in src/main.cpp, but sometimes these libraries depend each other.
In this case: client/User.hpp uses generator/IdGenerator.hpp
Project
│
├── CMakeLists.txt
├── libs
│   ├── CMakeLists.txt
│   ├── client
│   │   ├── CMakeLists.txt
│   │   ├── User.cpp
│   │   └── User.hpp
│   └── generator
│   ├── CMakeLists.txt
│   ├── IdGenerator.cpp
│   ├── IdGenerator.hpp
│   └── Types.hpp
└── src
└── main.cpp
Project/CMakeLists.txt:
cmake_minimum_required (VERSION 3.8)
project(game-project VERSION 0.1.0)
set (CMAKE_CXX_STANDARD 20)
set (CMAKE_CXX_FLAGS "-Wall -Wextra -O0 -std=c++20")
add_executable (game src/main.cpp)
add_subdirectory(libs)
target_link_libraries(game libclient libgenerator)
libs/CMakeLists.txt:
add_subdirectory(generator)
add_subdirectory(client)
libs/client/CMakeLists.txt:
add_library(libclient STATIC
User.cpp
User.hpp
)
include_directories(generator/)
target_link_libraries(libclient libgenerator)
libs/generator/CMakeLists.txt:
add_library(libgenerator STATIC
IdGenerator.cpp
IdGenerator.hpp
Types.hpp
)
C++ files:
main.cpp:
#include <client/User.hpp>
int main(int argc, const char* argv[])
{
User user;
return 0;
}
client/User.hpp:
#pragma once
#include <generator/IdGenerator.hpp>
class User
{
Identifier id = IdGenerator::generateId();
};
When I run make after cmake, I get this error:
In file included from Project/libs/client/User.cpp:1:
Project/libs/client/User.hpp:3:10: fatal error: generator/IdGenerator.hpp: No such file or directory
3 | #include <generator/IdGenerator.hpp>
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
Sorry for the verbose summary, I was able to shorten the example this much. What do you think the problem is?
This question might seem like a duplicate of CMake libraries that depend on each other but as you can see I'm already applying the
include_directories(generator/).
I misunderstood what the problem was initially, but now I think this might help.
Try adding to the CMakeLists.txt of your client instead of
include_directories(generator/)
this command
target_include_directories(libclient PUBLIC <Path to your generator file>)
Maybe you need to experiment a little to get the path correct as you might need to either specify it from the root directory or the current one (where this CMakeLists.txt resides), but it might solve your problem.

C++ library public interface as single header file

This is an example of a tuigraphics library I'm working on, what would be the best approach into creating it's public interface accessible via a single header file?
├── CMakeLists.txt
├── include
│   ├── Cell.hpp
│   ├── Grid.hpp
│   ├── math.hpp
│   ├── Node.hpp
│   ├── Renderer.hpp
│   ├── shape
│   │   ├── CircleShape.hpp
│   │   ├── RectangleShape.hpp
│   │   └── Shape.hpp
│   └── types.hpp
└── src
└── Node.cpp
I would like to access the library like so:
#include <tuigraphics.hpp>
int main(void)
{
Node node(CircleShape(10), Vector2(10, 10));
return EXIT_SUCCESS;
}
I have thought about putting a header file tuigraphics.hpp including all header files, but I'm not quite sure about it. I have never built a static library, and i would like some advice on how to proceed. Also here the CMake configuration:
project(libex)
cmake_minimum_required(VERSION 3.16)
add_library(tuigraphics STATIC)
target_include_directories(tuigraphics PRIVATE ./include)
target_sources(tuigraphics PRIVATE src/Node.cpp
include/Node.hpp
include/types.hpp
include/math.hpp
include/Cell.hpp
include/shape/CircleShape.hpp
include/shape/Shape.hpp
include/shape/RectangleShape.hpp
include/Renderer.hpp)
target_include_directories(tuigraphics PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

Decrease the length of headers with help of target_include_directories

I have a project
├── CMakeLists.txt
│   ├── log
│   │   ├── CMakeLists.txt
│   │   ├── include
│   │   │   ├── log.h
│   │   └── src
│   │   ├── log.cpp
│   └── main.cpp
In log.cpp I am usng #include "../include/log.h" and in main.cpp I amd using #include "include/log.h"
I want to use #include "log.h"
I read that target_include_directories can help me.
How can I apply it to my CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(Logger)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
#include_directories(log/include/) -- I used this, but I want to use target_include_directories
add_library(log_lib log/src/log.cpp)
add_executable(demo main.cpp)
target_link_libraries(demo log_lib)
define the target and then use the name as the first argument for target_include_directories
add_library(log_lib log/src/log.cpp)
target_include_directories(log_lib PUBLIC log/include)
worry about INTERFACE vs PUBLIC vs PRIVATE after you've got it all working and you want to understand it better. (This option transitively affects targets that depend on your library).
The line below helped
target_include_directories(log_lib PUBLIC log/include)

CMake include library in another library

I have a given project structure
.
├── CMakeLists.txt
├── lib
│   ├── lodepng
│   │   ├── CMakeLists.txt
│   │   └── src
│   │   ├── lodepng.cpp
│   │   └── lodepng.h
│   └── pixel_reader
│   ├── CMakeLists.txt
│   └── src
│   ├── hello.cpp
│   └── hello.h
├── main.cpp
With the following CMakeLists
./CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(pov_system VERSION 1.0)
add_subdirectory(lib/lodepng)
add_subdirectory(lib/pixel_reader)
add_executable(pov_system main.cpp)
target_link_libraries(pixel_reader PRIVATE lodepng)
target_link_libraries(pov_system PRIVATE pixel_reader)
./lodepng/CMakeLists.txt
add_library(
lodepng
src/lodepng.cpp
src/lodepng.h
)
target_include_directories(lodepng PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
./pixel_reader/CMakeLists.txt
add_library(
pixel_reader SHARED
src/hello.cpp
src/hello.h
)
target_include_directories(pixel_reader PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
As one can see, I try to link the 'lodepng' library to the 'pixel_reader' library and include the 'lodepng.h' to the 'hello.h' file.
But at the moment I get the following error while trying to build the project.
[build] <path-to-project>/pov_system/lib/pixel_reader/src/hello.h:2:10: fatal error: lodepng.h: No such file or directory
[build] 2 | #include "lodepng.h"
[build] | ^~~~~~~~~~~
[build] compilation terminated.
Question
Why is my code not finding the 'lodepng.h' file or (and even more important) is it a good practice to link from one library to another?
Maybe two really simple questions, but just started to dive into the world of CMake, Compiling, etc... and I really appreciate your help.
Why is my code not finding the 'lodepng.h' file or (and even more important)
Because you probably didn't give it correct path. One way to fix that would be to give the exact path in hello.h
#include "../../lodepng/src/lodepng.h
Second way is to use target_include_directories:
target_include_directories(pixel_reader PUBLIC "../../lodepng/src/")
is it a good practice to link from one library to another?
It depends on your project. If library A requires library B, then yes, it is okay in my opinion.
More importantly, you are creating the target in the wrong place i.e., in the root CMakeLists file. It must be done in the directory in which target is created.
./pixel_reader/CMakeLists.txt
# create target
add_library(
pixel_reader SHARED
src/hello.cpp
src/hello.h
)
target_link_libraries(pixel_reader PRIVATE lodepng) #link library where target is created
target_include_directories(pixel_reader PUBLIC "../../lodepng/src/")
target_include_directories(pixel_reader PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
Your pixel_reader library target possibly needs lodepng.h header to compile, because it depends on it.
something like
target_include_directories(pixel_reader PUBLIC "PATH_TO_LODE_PNG_HEADER_DIRECTORY")
could solve this problem.

cmake target_link_libraries() cannot find renamed lib target by set_target_properties(archive_output_name)

RT~ ps: cmake version 3.9.2
My codebase just like this.
suzanwen#n224-004-133:~/repos/C++/ttt:)$ tree -L 2
.
├── build
│   ├── bin
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── cmake_install.cmake
│   ├── lib
│   ├── Makefile
│   ├── test
│   └── thirdparty
├── build.sh
├── CMakeLists.txt
├── Makefile
├── test
│   ├── CMakeLists.txt
│   └── main.cc
└── thirdparty
├── CMakeLists.txt
├── gflags
└── hellolib
10 directories, 9 files
my thirdparty/hellolib/CMakeLists.txt is
PROJECT(hello)
SET(LIBHELLO_SRC hello.cc)
MESSAGE(STATUS "LIBRARY PATH=" ${LIBRARY_OUTPUT_PATH})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello_static PROPERTIES ARCHIVE_OUTPUT_NAME "hello")
my test/CMakeLists.txt is
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/thirdparty/hellolib
${PROJECT_SOURCE_DIR}/thirdparty/gflags/include)
IF(LIBRARY_OUTPUT_PATH)
LINK_DIRECTORIES(${LIBRARY_OUTPUT_PATH})
ENDIF(LIBRARY_OUTPUT_PATH)
ADD_EXECUTABLE(main main.cc)
TARGET_LINK_LIBRARIES(main hello)
# TARGET_LINK_LIBRARIES(main hello_static)
when I build my top-level project, an error occurs like this.
/usr/bin/c++ -rdynamic CMakeFiles/main.dir/main.cc.o -o ../bin/main -L/home/suzanwen/repos/C++/ttt/build/lib -Wl,-rpath,/home/suzanwen/repos/C++/ttt/build/lib -lhello
/usr/bin/ld: cannot find -lhello
But when I comment the line # SET_TARGET_PROPERTIES(hello_static PROPERTIES ARCHIVE_OUTPUT_NAME "hello") and TARGET_LINK_LIBRARIES with hello_static, everything goes fine.
It seems that TARGET_LINK_LIBRARIES cannot find renamed lib target. Could anyone explain it? thanks in advance.
It seems that TARGET_LINK_LIBRARIES cannot find renamed lib target.
Setting ARCHIVE_OUTPUT_NAME property renames not a target, but an output file. So linking with a target still works:
TARGET_LINK_LIBRARIES(main hello_static)
One cannot rename the target once it is created, but it is possible to create ALIAS for a target:
ADD_LIBRARY(hello ALIAS hello_static)
After that it is possible to link with the alias:
TARGET_LINK_LIBRARIES(main hello)