how to write cmake with 1 executable and multiple subdirectories - c++

I have a project with many sub-directories that each 1 of them include both header and source files. I want 1 executable which include all my src files.
I managed to write the cmake so it compiles successfully,
but Im dealing with 2 problems, I dont now how to solve:
1 - The compile time is very slow, probably somthing I'm doing wrong.
2 - I have few flags like DEBUG, RELEASE that I couldnt make it define/ undefine in any of the CMakeLists.txt files.
I need this because I have some of my source files :
#ifndef RELEASE /DEBUG #endif
My project stracture looks similar to this:
root/ (project root)
3rd_party/
spdlog/ ...
src/
CMakeLists.txt
main.cpp (contains main method)
logger/
log.c
logger.cpp
logger.hpp
CMakeLists.txt
first/
first_c.h
first.cpp
first.hpp
CMakeLists.txt
second/
second.cpp
second.hpp
CMakeLists.txt
control/
control.cpp
control.hpp
control_2.hpp
CMakeLists.txt
some more sub directories/...
build/
... (executable)
This is part from what I currently have :
in src/CMakeLists.txt:
cmake_minimum_required(VERSION 3.18)
project(integration LANGUAGES CXX)
set(src_main main.cpp)
add_subdirectory(first)
add_subdirectory(second)
add_subdirectory(third)
add_subdirectory(control)
add_subdirectory(logger)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../3rd_party/spdlog ${CMAKE_CURRENT_BINARY_DIR}/spdlog)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
add_executable(${PROJECT_NAME} ${src_main})
target_link_libraries(${PROJECT_NAME} control first second third logger)
target_compile_definitions(${PROJECT_NAME} PUBLIC RELEASE) # not working???
in src/control/CMakeLists.txt:
set(control_source_files
control.cpp
control.hpp
)
add_library(control SHARED ${control_source_files})
find_library(LIB paho-mqtt3c ${PROJECT_SOURCE_DIR}/lib)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(control PUBLIC Threads::Threads)
target_link_libraries(control PUBLIC ${LIB})
target_include_directories(robot_control PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../second)
target_include_directories(robot_control PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../third)
in src/first/CMakeLists.txt:
set(first_source_files
first_c.h
first.cpp
first.hpp
)
add_library(first SHARED ${first_source_files})
target_include_directories(motor PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../second)
target_include_directories(motor PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../logger)
target_include_directories(motor PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../3rd_party/date/include/)
Im not sure if I need target_include_directories I did it becuase in first.hpp there are icludes for headers in those subdirectories.
any help to do the compile time faster and define right the flags ??

1 - The compile time is very slow, probably somthing I'm doing wrong.
There might be many reasons.
Main cause is usually to many includes in header files. Froward declarations in as many places as possible of header files is very effective cure for that issue. There is even a tool which helps to clean up existing project Include What You Use (IWYU).
2 - I have few flags like DEBUG, RELEASE that I couldnt make it
define/ undefine in any of the CMakeLists.txt files. I need this
because I have some of my source files :
target_compile_definitions(${PROJECT_NAME} PUBLIC RELEASE) # not working???
You are doing that wrong. Depending on generator you use (if it supports multiple configuration or not), you can control this in configuration step or in build step, by passing extra parameters:
In case if generator do not have mtuple configurations you configure this this way:
cd build
cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake --build .
See doc
If generator supports multiple configurations then:
cd build
cmake ..
cmake --build . --config Debug
target_include_directories(robot_control PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../second)
target_include_directories(robot_control PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../third)
This is wrong. target_include_directories defines which paths are important for given target (library). Now when something is PUBLIC then any target which will do target_link_libraries(someTarget PUBLIC sourceTarget) where sourceTarget is your library will be impacted by this path. someTarget will inherit include paths from public (or interface) of source target so you don't have to add them explicitly.
So basically this quted lines are obsolete (if second and third targets have this include paths defined as PUBLIC).

Related

Why are files not found from parallel folders CMake

To have my .cpp and .h files a little bit sorted up pending on their responsibilitie I decided to put them into seperate folders
I used the following structure:
root
|
-CMakeLists.txt [rootCmakeList]
src
|
-main.cpp
.......|
....... math
.......|
.......-CMakeLists.txt[mathCmakeList]
.......-Algebra.h
.......-Algebra.cpp
.......XML[xmlCmakeList]
.......|
.......-CMakeLists.txt
.......-AwesomeXML.h
.......-AwesomeXML.cpp
The [rootCmakeList] looks:
cmake_minimum_required(VERSION 3.11.3)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_FLAGS_DEBUG "-g")
project(myProject)
#add exectuable
add_executable(myProject src/main.cpp)
target_include_directories(myProject PUBLIC "${src/XML}")
target_include_directories(myProject PUBLIC "${src/math}")
add_subdirectory(src/XML)
add_subdirectory(src/math)
target_link_libraries(myProject xml)#needed?
target_link_libraries(myProject math)#needed?
The [mathCmakeList] looks:
include_directories(${myProject}src/math)
add_library(math Algebra.cpp)
The [xmlCmakeList] looks:
include_directories(${myProject}src/xml)
add_library(xml AwesomeXML.cpp)
So far so good and no problems. But if I want to #include Algebra.h into
AweseomeXML.cpp I can not find the file.
To being honest I am not even sure if the cmake command add_library and target_link_libraries makes really sense here because I do not want to create own libraries of it just want to tidy up a little bit my files pending on their topic.
myProject doesn't seem to be a variable you set. Furthermore if you properly set up the library targets, you won't need to add any of the include directories to myProject manually.
First set up the CMakeLists.txt file for math, since it doesn't depend on other projects. I recommend moving headers linking libraries use to a subdirectory. I myself usually use include and any path that you want the linking library to use in #includes starts there. Set the include dirs in a way that adds them to the INTERFACE_INCLUDE_DIRECTORIES target property of math.
add_library(math Algebra.cpp
include/Algebra.h # for IDEs
)
target_include_directories(math # should be static here, since you don't want to deploy the lib by itself
PUBLIC include # available to both math and linking libs
PRIVATE . # only available to math; only necessary, if there are "private" headers
)
Then do the same thing for xml, but since you're using functionality from math, you need to link it giving you access to its include dirs automatically, since they are available via INTERFACE_INCLUDE_DIRECTORIES:
add_library(xml STATIC
AwesomeXML.cpp
include/AwesomeXML.h
)
target_include_directories(xml PUBLIC include)
target_link_libraries(xml PRIVATE math) # change PRIVATE to PUBLIC, if Algebra.h is included in a public header
Now at the toplevel we should make sure that any dependencies of a target are available before the target is defined. Also linking a lib gives you access to their respective public include directories and the public include dirs of dependencies linked publicly:
cmake_minimum_required(VERSION 3.11.3)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_FLAGS_DEBUG "-g")
project(myProject)
add_subdirectory(src/math)
add_subdirectory(src/XML)
#add exectuable
add_executable(myProject src/main.cpp)
target_link_libraries(myProject PRIVATE math xml)

How to properly create cmake library for both program and test usage and compile it only once

I have code with one big directory of models which are going to be used in two binaries - the main program and tests. I want to put the models to the library which will compile once and then both main program and tests can use it without recompiling it again.
Here are my current directories:
root/
CMakeLists.cpp
src/
CMakeLists.txt
main.cpp
model/
/*a lot of cpp/hpp files*/
impl/
impl.cpp
impl.hpp (uses models)
test/
CMakeLists.txt
main.cpp
test.cpp (uses models and impl)
root/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(server)
set(CMAKE_CXX_STANDARD 14)
file(GLOB MODEL_SOURCES "src/model/*.cpp")
add_library(modelLibrary ${MODEL_SOURCES})
add_subdirectory(src)
add_subdirectory(test)
src/CMakeLists.txt
set(BINARY ${CMAKE_PROJECT_NAME})
file(GLOB_RECURSE SOURCES LIST_DIRECTORIES true *.h *.cpp)
set(SOURCES ${SOURCES})
add_executable(${BINARY} ${SOURCES})
include_directories(impl)
# can't see model files without this line
target_include_directories(${BINARY} PRIVATE ./model)
target_link_libraries(${BINARY} PUBLIC modelLibrary pistache pthread ssl crypto)
test/CMakeLists.txt
set(BINARY ${CMAKE_PROJECT_NAME}_test)
file(GLOB_RECURSE TEST_SOURCES LIST_DIRECTORIES false *.hpp *.cpp)
set(SOURCES ${TEST_SOURCES})
add_executable(${BINARY} ${TEST_SOURCES})
add_test(NAME ${BINARY} COMMAND ${BINARY})
# can't see model files without this line
target_include_directories(${BINARY} PRIVATE ../src/model)
target_link_libraries(${BINARY} modelLibrary gtest gtest_main pthread)
I have failed with using modelLibrary as only source. It still compiles the models two times. I'd like to achieve solution where models are compiled once and they are reachable from both - main program and test.
I believe the reason you see the model files compiled twice is due to the recursive glob in src/CMakeLists.txt.
file(GLOB_RECURSE SOURCES LIST_DIRECTORIES true *.h *.cpp)
This recursive glob will also walk into src/model and grab the *.cpp files there. So the model files are getting compiled as part of modelLibrary as well as part of the server executable.
One way to fix this would be to remove the recursive glob and create a separate CMakeLists.txt in src/model with the contents of:
# I wouldn't actually glob, see note below.
file(GLOB MODEL_SOURCES "*.cpp")
add_library(modelLibrary ${MODEL_SOURCES})
target_include_directories(modelLibrary PUBLIC ".")
One would need to make a call add_subdirectory() for this new CMakeLists in either the root or the src/CMakeLists.txt.
Notice the use of target_include_directories in the possible solution. I put that there as I noticed it was being repeated in the test and executable. Using PUBLIC on modelLibrary means that consumers get the include directory just by using target_link_libraries.
Note about globbing: from CMake docs on GLOB
Note We do not recommend using GLOB to collect a list of source files from your source tree. If no CMakeLists.txt file changes when a source is added or removed then the generated build system cannot know when to ask CMake to regenerate. The CONFIGURE_DEPENDS flag may not work reliably on all generators, or if a new generator is added in the future that cannot support it, projects using it will be stuck. Even if CONFIGURE_DEPENDS works reliably, there is still a cost to perform the check on every rebuild.
That note can be a bit hard to grok, but try to fully understand the consequences if you do decide to glob.

Integrating GTest with existing CMake Project: share the same target_sources

I have a large C++ library, and want to do some testing with GTest.
At the moment, the build is handled with CMake, in particular there is one CMakeLists.txt file in the root directory like the following
make_minimum_required(VERSION 3.13.0)
project(mylib)
find_package(PkgConfig REQUIRED)
set(CMAKE_BUILD_WITH_INSTALL_RPATH ON)
set(CMAKE_INSTALL_RPATH "${CMAKE_CURRENT_SOURCE_DIR}/lib/protobuf/src/.libs/")
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
SET(BASEPATH "${CMAKE_SOURCE_DIR}")
INCLUDE_DIRECTORIES("${BASEPATH}")
add_executable(mylib run.cpp)
add_subdirectory(src)
add_subdirectory(proto)
target_include_directories(mylib PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/lib/math
${CMAKE_CURRENT_SOURCE_DIR}/lib/protobuf/src
... some dirs ...
)
target_link_directories(mylib PRIVATE
... some libs ..
)
target_link_libraries(mylib
${CMAKE_CURRENT_SOURCE_DIR}/lib/math
${CMAKE_CURRENT_SOURCE_DIR}/lib/protobuf/src
.....
)
target_compile_options(mylib PUBLIC -D_REENTRANT -fPIC)
Then in the src directory and every sub-directory there is a CMakeLists.txt file, for example this is in src/
target_sources(mylib
PUBLIC
includes.hpp
)
add_subdirectory(algorithms)
add_subdirectory(collectors)
add_subdirectory(hierarchies)
add_subdirectory(mixings)
add_subdirectory(runtime)
add_subdirectory(utils)
My question here is the following: what is the least painful way to integrate GTest in the current project? I was thinking of having a test/ subdirectory, like I've seen here: Adding Googletest To Existing CMake Project
However this example requires that for each executable you manually list all the files it includes. Is there a quicker way to use the sources that are already added to 'mylib'?
You can split the current mylib executable target into two targets
mylib, a library target that is very much like the current mylib target, but without the run.cpp file
mylib_exe an executable target that compiles run.cpp and links to mylib
Now your test files can link to mylib.

How to use find_package on a package added from top level add_subdirectory?

This might be an x y problem, so here's my situation.
Background
I have the following project structure:
-project
-examples
-example_that_uses_mylib_1
* CMakeLists.txt
* main.cpp
-example_that_uses_mylib_2
* CMakeLists.txt
* main.cpp
-external
-notmylib_a
* CMakeLists.txt
* ... (other stuff)
-notmylib_b
* CMakeLists.txt
* ... (other stuff)
-src
-mylib_stuff
* file1.cpp
* file1.h
*CMakeLists.txt
CMakeLists.txt
I'm attempting to make a cmake file that does the following:
allows mylib to depend on targets in 3rd party libraries found in external with out using add_subdirectory given that they aren't actually sub directories and that is bad practice.
allows example_that_uses_mylib executables that depend on mylib target with out adding it as a sub directory.
I was unsure of how to do this until I saw this project whos top level cmake does this:
add_subdirectory(lib/foo)
add_subdirectory(src/bar)
add_subdirectory(src/baz)
and bar and baz CMakeLists.txt do this:
#Bar
find_package(foo 0.1.2 CONFIG REQUIRED)
#Baz
find_package(bar CONFIG REQUIRED)
Which made me think I could do the same with my libraries. I couldn't.
Originally the single top level CMakeLists.txt built all targets, I wanted to move away from this, and split up building with add_subdirectories, starting with mylib.
Problem
Originally my top level CMakeLists.txt looked a lot like this (which previously worked):
add_subdirectory(external/notmylib_a)
add_library(mylib STATIC src/mylib_stuff/file1.cpp)
target_include_directories(mylib PUBLIC src/)
target_link_libraries(mylib PRIVATE notmylib_a::notmylib_a)
when I decided to split things up so originally I did this (which also works):
#CMakeLists.txt
add_subdirectory(external/notmylib_a)
#src/CMakeLists.txt
add_library(mylib STATIC src/mylib_stuff/file1.cpp)
target_include_directories(mylib PUBLIC src/)
target_link_libraries(mylib PRIVATE notmylib_a::notmylib_a)
Then to follow the other project I decided to do this:
#CMakeLists.txt
add_subdirectory(external/notmylib_a)
#src/CMakeLists.txt
find_package(notmylib_a CONFIG REQUIRED) #NEW LINE!!
add_library(mylib STATIC src/mylib_stuff/file1.cpp)
target_include_directories(mylib PUBLIC src/)
target_link_libraries(mylib PRIVATE notmylib_a::notmylib_a)
and I got an error in CMAKE
CMake Error at src/CMakeLists.txt:25 (find_package):
Could not find a package configuration file provided by "notmylib_a"
with any of the following names:
notmylib_aConfig.cmake
notmylib_a-config.cmake
Add the installation prefix of "notmylib_a" to CMAKE_PREFIX_PATH or set
"notmylib_a_DIR" to a directory containing one of the above files. If
"notmylib_a" provides a separate development package or SDK, be sure it
has been installed.
How was the other project able to utilize find_package in such a way?
How was the other project able to utilize find_package in such a way?
Config file for other foo project has these lines:
if(NOT TARGET foo::foo)
include("${foo_CMAKE_DIR}/foo-targets.cmake")
endif()
That is, when the foo is included with add_subdirectory approach and creates foo::foo target, find_package(foo) actually ignores its configuration file.
This is noted in the foo's CMakeLists.txt:
# We also add an alias definition so that we shadown
# the export namespace when using add_subdirectory() instead.
add_library(foo::foo ALIAS foo)
In other words, with given foo package included with add_subdirectory approach, using find_package(foo) is possible, but optional: One may directly use foo or foo::foo target without any find_package().
The example project that you refer to is able to do:
#Bar
find_package(foo 0.1.2 CONFIG REQUIRED)
#Baz
find_package(bar CONFIG REQUIRED)
because both of the sub-projects lib/foo and src/bar have CMakeLists.txt files
that contain CMake code that generates a CMake package configuration file
that will be searched for by find_package when CONFIG mode is specified,
discovery of which will constitute success for the find_package command.
In lib/foo/CMakeLists.txt, for example, such code includes:
include(CMakePackageConfigHelpers)
...
set(PROJECT_CONFIG_FILE "${PROJECT_BINARY_DIR}/foo-config.cmake")
...
configure_package_config_file(cmake/foo-config.cmake.in
${PROJECT_CONFIG_FILE}
INSTALL_DESTINATION ${INSTALL_CONFIG_DIR})
As a result, when cmake is run to generate the project build files, the
package configuration file is among the files generated, like so:
$ git clone https://github.com/sunsided/cmake.git
...
$ cd cmake/
$ mkdir build
$ cd build
$ cmake ..
...
$ find -name '*-config.cmake'
./lib/foo/foo-config.cmake
./src/bar/bar-config.cmake
Those two *-config.cmake files are respectively the package configuration
files for lib/foo and src/bar. The documentation for find_package CONFIG mode
describes the (complex) search algorithm by which find_package will discover them. For sub-project
src/bar,
find_package(foo 0.1.2 CONFIG REQUIRED)
is able to find lib/foo/foo-config.cmake because, when it runs, the build files
for its dependency lib/foo have already been generated. Likewise for sub-project
src/baz,
find_package(bar CONFIG REQUIRED)
succeeds because the build files for its dependency src/bar have already been
generated.
The CMake error that you receive from your own attempt to use find_package in the same way:
CMake Error at src/CMakeLists.txt:25 (find_package):
Could not find a package configuration file provided by "notmylib_a"
with any of the following names:
notmylib_aConfig.cmake
notmylib_a-config.cmake
...
now has an obvious meaning. To fix it, you need to fill in the missing
CMake code in the external/notmylib_a/CMakeLists.txt to generate notmylib_a-config.cmake.

how to use cmake to compile modern c++ project code

here's my source code directory structure.
some project
libs
mylib1
...
3rdlibs1
...
apps
myapp1
...
My requirments are as follows:
3rdlibs1 should be compiled when using command. like "cmake -G ...".
3rdlibs1 should be compiled as static library.
mylib1 should be modular.
mylib1 depends on 3rdlibs.
myapp depends on mylib1, and it should only link to mylib1. It shouldn't depends on 3rdlibs or system libraries.
Can you give me some sample code. I know ExternalProject_Add can help me to compile 3rdlibs. But I really don't know how to do it.
I think other people may also be interesting to this question.
If you have all your sources in single file system tree, it is better to use add_subdirectory than ExternalProject. ExternalProject is for projects which are truly external, e.g. on remote server or VCS repository. Unlike add_subdirectory which can create target of any type, ExternalProject can create only UTILITY target, similar to add_custom_target command. UTILITY targets have limitations, e.g. you can not use them in target_link_libraries command. Using add_subdirectory is much simpler.
Top-level:
cmake_minimum_required(VERSION 3.7)
project("some_project")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(libs/3rdlibs1)
add_subdirectory(libs/mylib1)
add_subdirectory(apps/myapp1)
App:
project("myapp1")
set(SRC_FILES ...)
add_executable(myapp1 ${SRC_FILES})
target_link_libraries(myapp1 PRIVATE mylib1)
Lib. As I understand from your description, 3rdlibs1 is optional dependency of mylib1. Otherwise, how can myapp use mylib1 without 3rdlibs1?
project("mylib1")
option(THIRD_LIBS_SUPPORT "description" OFF)
set(SRC_FILES ...)
add_library(mylib1 STATIC ${SRC_FILES})
#PUBLIC means that both mylib1 and its dependents use the headers
target_include_directories(mylib1 PUBLIC "${PROJECT_SOURCE_DIR}/include")
if(THIRD_LIBS_SUPPORT)
#PUBLIC means that 3rdlibs1 will be linked to mylib1 dependents
target_link_libraries(mylib1 PUBLIC 3rdlibs1)
target_compile_definitions(mylib1 PUBLIC -DTHIRD_LIBS_SUPPORT)
endif()