CMake split target_sources in CMakeLists.txt into different targets - c++

I have a test project called test_containers. Inside directory there are only cpp files and CMakeLists.txt which looks like this:
cmake_minimum_required(VERSION 3.0.0)
project(test_containers)
add_executable(${PROJECT_NAME})
target_sources(${PROJECT_NAME} PRIVATE
test_linear_hash_table.cpp
test_list.cpp
test_stack.cpp
test_string.cpp
test_vector.cpp
test_set.cpp
test_array.cpp
test_span.cpp
)
target_link_libraries(${PROJECT_NAME} PRIVATE
gtest_main
)
add_test(
NAME ${PROJECT_NAME}
COMMAND ${PROJECT_NAME}
)
Now the problem is that after build I get only 1 executable bin file called test_containers and it contains all the tests from directory. I want to split this CMakeLists.txt into different targets so that these are different tests. How to do it right?

For your question, as described, you will need to create 1 target per file -- each building the individual source, linking the correct dependencies, and being added as a test.
You might be able to do something like (untested):
set(source_files
test_linear_hash_table.cpp
test_list.cpp
test_stack.cpp
test_string.cpp
test_vector.cpp
test_set.cpp
test_array.cpp
test_span.cpp
)
# Iterate each source
foreach(source IN LISTS source_files)
# Add the executable + source + dependencies
add_executable(${PROJECT_NAME}.${source})
target_sources(${PROJECT_NAME}.${source} PRIVATE ${source})
target_link_libraries(${PROJECT_NAME}.${source} PRIVATE gtest_main)
# Add a test
add_test(
NAME ${PROJECT_NAME}.${source}
COMMAND ${PROJECT_NAME}.${source}
)
endforeach()
(This assumes ${source} doesn't use any special or invalid characters for target names)
However, generally when people ask this question, it's because they would actually like 1 CTest test-case per test in a GTest executable. This would allow the CTest dashboard to show each of the different tests independently, which makes scanning for failures quite easy.
If this is what you are looking for, then there's already a module/function that can provide this for you with little effort. You can use the GoogleTest module with the gtest_discover_tests function, which will produce 1 CTest test-case per GTest test-case:
include(GoogleTest)
gtest_discover_tests(${PROJECT_NAME})

Related

How to configure cmake to recompile a target when a non .cpp source file is modified

If we look at the minimal example below,
cmake_minimum_required(VERSION 3.20)
project(example)
add_executable(${PROJECT_NAME} main.cpp test.txt)
Once the executable target is built, it will only rebuild if main.cpp is modified. If test.txt is modified, it wouldn't rebuild because eventhough test.txt is included as a source for the executable target, it isn't used to compile the executable. Is there any way that we can configure cmake so that when test.txt is modified, it will trigger a rebuild?
The real use case for my application is I have a metal file that is associated with an executable target (e.g. add_executable(${PROJECT_NAME} main.cpp mylib.metal)) and I want to generate mylib.metallib along with the example executable when the target is build. I have something like
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND "the command to compile .metal into .metallib")
but this add_custom_command will only be invoked during the first compilation of the executable target, or whenever main.cpp is modified, I want this add_custom_command to be invoked also when mylib.metal is modified. How can this be done?
One way is to create a custom target and add a custom command to it that will generate your mylib.metallib
cmake_minimum_required(VERSION 3.20)
project(custom_file_target VERSION 1.0.0)
add_executable(main main.cpp) # main target
add_custom_target(
custom
DEPENDS ${CMAKE_BINARY_DIR}/mylib.metallib
)
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/mylib.metallib
COMMAND echo your command that will create mylib.metallib
DEPENDS ${CMAKE_SOURCE_DIR}/mylib.metal
)
add_dependencies(main custom)
You can swap main and custom in the last line depending on what dependency ordering you want (main depends on custom or the other way round).
See also my other answer here: https://stackoverflow.com/a/70626372/8088550
If mylib.metallib is actually linkable you can also think about creating an imported library that depends on your custom target, i.e.
cmake_minimum_required(VERSION 3.20)
project(custom_file_target VERSION 1.0.0)
add_executable(main main.cpp) # main target
add_custom_target(
custom
DEPENDS ${CMAKE_BINARY_DIR}/mylib.metallib
)
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/mylib.metallib
COMMAND echo your command that will create mylib.metallib
DEPENDS ${CMAKE_SOURCE_DIR}/mylib.metal
)
add_library(mylib STATIC IMPORTED)
set_property(
TARGET mylib PROPERTY
IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/mylib.metallib
)
add_dependencies(mylib custom)
target_link_libraries(main PRIVATE mylib)

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.

CMake Add (Test) Executable

I would like to create two executables: one executable for the application, and one for the testing of the application. To that end, I have the following in my CMakeLists.txt file:
include_directories(include)
file(GLOB SOURCE "src/*.cc")
file(GLOB TEST "test/*.cc")
add_executable(interest_calc ${SOURCE})
add_executable(interest_calc_test "src/interest_calc.cc" ${TEST})
Since both src and test directories contain main functions, I have to manually add source files to the "test" executable. Is there another, non-manual, way to add required source files to the "test" executable?
Further, is there a better way to test functionality than creating a separate test executable? If so, what/how?
One way to improve your process would be to pull the guts of your executable into a library, then have a nominal "main" executable which just calls into your library and a "test" executable which exercises the library however you want to test it.
This way, any changes you need to make go into the library and the executable build process is untouched.
Edit to show CMake with your example:
include_directories(include)
file(GLOB SOURCE "src/*.cc")
# Remove main from library, only needed for exec.
list(REMOVE_ITEM SOURCE "main.cc")
file(GLOB TEST "test/*.cc")
add_library(interest_calc_lib STATIC ${SOURCE})
add_executable(interest_calc "main.cc")
target_link_libraries(interest_calc interest_calc_lib)
add_executable(interest_calc_test ${TEST})
target_link_libraries(interest_calc_test interest_calc_lib)
There are already some good answers from Soeren and mascoj but I would like to give a more concrete recommendation.
When you already have a CMakeLists.txt for your executable and you like to add testing, I recommend adding a static dummy library. This library can have all the sources of the executable except the main method (it may be easiest to single out the main method in a separate file if you do not have that already). Using a static library will give you two benefits:
The final executable will behave exactly as your current one, so there is no need to deal with distribution of a new, shared library
You do not need to deal with exporting symbols or throwing exceptions across shared object boundaries
The changes to your CMakeLists.txt can be quite small. I will give an example here, assuming you use cmake 3.0 or newer. First, an example of CMakeLists.txt before adding the dummy library:
project(MyProject)
set(SOURCES src/First.cc src/Second.cc src/Third.cc)
add_executable(${PROJECT_NAME} ${SOURCES} src/Main.cc)
target_include_directories(${PROJECT_NAME}
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_BINARY_DIR})
target_compile_options(${PROJECT_NAME}
$<$<CXX_COMPILER_ID:GNU>:-Wall;-pedantic)
target_compile_definitions(${PROJECT_NAME}
$<$<CONFIG:Debug>:DEBUG;_DEBUG>)
set_target_properties(${PROJECT_NAME}
PROPERTIES CXX_STANDARD 14)
target_link_libraries(${PROJECT_NAME}
Threads::Threads)
To add the dummy library and testing, you need to introduce a new target with a different name. I choose here to use ${PROJECT_NAME}_lib because this will be very non-intrusive on the CMakeLists.txt. Here is the updated version. Notice the use of ${PROJECT_NAME}_lib in place of ${PROJECT_NAME} in almost all places. Most properties are now passed down to the executable by making them PUBLIC. Only calls to set_target_properties() are not transitive and must be duplicated for library and executable.
project(MyProject)
set(SOURCES src/First.cc src/Second.cc src/Third.cc)
add_library(${PROJECT_NAME}_lib STATIC ${SOURCES})
add_executable(${PROJECT_NAME} src/Main.cc)
target_include_directories(${PROJECT_NAME}_lib PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_BINARY_DIR})
target_compile_options(${PROJECT_NAME}_lib PUBLIC
$<$<CXX_COMPILER_ID:GNU>:-Wall;-pedantic)
target_compile_definitions(${PROJECT_NAME}_lib PUBLIC
$<$<CONFIG:Debug>:DEBUG;_DEBUG>)
set_target_properties(${PROJECT_NAME}_lib
PROPERTIES CXX_STANDARD 14)
set_target_properties(${PROJECT_NAME}
PROPERTIES CXX_STANDARD 14)
target_link_libraries(${PROJECT_NAME}
${PROJECT_NAME}_lib
Threads::Threads)
Now you can link your tests against ${PROJECT_NAME}_lib with a different main method.
You could do like this :
In the current CMakeLists.txt, put these lines :
add_subdirectory(src)
add_subdirectory(test)
and then, in each directories add a CMakeLists.txt that link correctly sources to each files.
About test, I've heard that CMake can do test automation, but I don't really know how it works.
In my opinion the best solution is to create a library (shared or static) and two executables (one for the main program and one for the test main). After that you should link the library against the two applications.
In this answer I write down a explanation with a little example how you could managed the project with cmake.

Deciding what to write in CMakeLists.txt - mainly in subfolders

I have a simple project going that uses CMake and gtest. I have a basic CMakeLists.txt file working, but I want to get a better understanding of how to use multiple CMakeLists.txt's and connect them. The code so far for the project is like this:
https://github.com/dmonopoly/writeart/tree/10b62048e6eb6a6ddd0658123d85ce4f5f601178
For quicker reference, the only CMakeLists.txt file (in the project root) that I take advantage of has this inside:
cmake_minimum_required(VERSION 2.8)
# Options
option(TEST "Build all tests." OFF) # makes boolean 'TEST' available
# Make PROJECT_SOURCE_DIR, PROJECT_BINARY_DIR, and PROJECT_NAME available
set(PROJECT_NAME MyProject)
project(${PROJECT_NAME})
set(CMAKE_CXX_FLAGS "-g") # -Wall")
#set(COMMON_INCLUDES ${PROJECT_SOURCE_DIR}/include) if you want your own include/ directory
# then you can do include_directories(${COMMON_INCLUDES}) in other cmakelists.txt files
################################
# Normal Libraries & Executables
################################
add_library(standard_lib Standard.cpp Standard.h)
add_library(converter_lib Converter.cpp Converter.h)
add_executable(main Main.cpp)
target_link_libraries(main standard_lib converter_lib)
################################
# Testing
################################
if (TEST)
# This adds another subdirectory, which has project(gtest)
add_subdirectory(lib/gtest-1.6.0)
enable_testing()
# Include the gtest library
# gtest_SOURCE_DIR is available due to project(gtest) above
include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
##############
# Unit Tests
##############
# Naming
set(UNIT_TESTS runUnitTests)
add_executable(${UNIT_TESTS} ConverterTest.cpp)
# standard linking to gtest stuff
target_link_libraries(${UNIT_TESTS} gtest gtest_main)
# extra linking for the project
target_link_libraries(${UNIT_TESTS} standard_lib converter_lib)
# This is so you can do 'make test' to see all your tests run, instead of manually running the executable runUnitTests to see those specific tests.
add_test(NAME myUnitTests COMMAND runUnitTests)
endif()
My goal is to move Standard.cpp and Standard.h into lib/. The moment I do this, though, I find the ordering of what I do in my CMakeLists.txt complicated. I need the library for my gtest setup, but the library would have to get made in lib/CMakeLists.txt, right? Wouldn't it easily become really complicated to find where all your libraries are and executables are as you would have to look through all your CMakeLists.txt's?
If I am missing something conceptually, or if there is a good example I could use to solve this easily, that would be great.
Help appreciated, and thanks in advance.
If you don't want to use multiple CMakeLists.txt files, don't.
################################
# Normal Libraries & Executables
################################
add_library(standard_lib lib/Standard.cpp lib/Standard.h)
add_library(converter_lib lib/Converter.cpp lib/Converter.h)
# Main.cpp needs to know where "Standard.h" is for the #include,
# so we tell it to search this directory too.
include_directories(lib)
If you do want multiple CMakeLists.txt, you'd move it out:
# Main CMakeLists.txt:
add_subdirectory(lib)
include_directories (${standard_lib_SOURCE_DIR}/standard_lib)
link_directories (${standard_lib_BINARY_DIR}/standard_lib)
and in /lib/CMakeLists.txt:
add_library (standard_lib Standard.cpp)
Here's an example.