Grouping the unit tests together in CMake - unit-testing

I have different test builds within CMake, with the last one running ALL the tests.
Does the last test UTestAll build each unit test separately and if so, does that mean the same test ends up getting built multiple times? Perhaps there's a way to group the unit tests such that it's only built once?
Or rather, is there a way to have the option to exclude a particular target from running? So say at some point I wouldn't want to run UTestAll when running on jenkins pipeline but would want to run locally.
#Test1
set(THIS UTest1)
string(TOUPPER DO_BUILD_${THIS} THIS_FLAG)
if (${THIS_FLAG})
add_executable(
${THIS}
src/Test1.cpp
)
target_include_directories(${THIS} PRIVATE include)
target_link_libraries(${THIS} PUBLIC lib1
endif()
#Test2
set(THIS UTest2)
string(TOUPPER DO_BUILD_${THIS} THIS_FLAG)
if (${THIS_FLAG})
add_executable(
${THIS}
src/Test2.cpp
)
target_include_directories(${THIS} PRIVATE include)
target_link_libraries(${THIS} PUBLIC lib2
endif()
#Test3
set(THIS UTest3)
string(TOUPPER DO_BUILD_${THIS} THIS_FLAG)
if (${THIS_FLAG})
add_executable(
${THIS}
src/Test3.cpp
)
target_include_directories(${THIS} PRIVATE include)
target_link_libraries(${THIS} PUBLIC lib3
endif()
# Run ALL Tests
set(THIS UTestAll)
string(TOUPPER DO_BUILD_${THIS} THIS_FLAG)
if (${THIS_FLAG})
add_executable(
${THIS}
src/Test1.cpp
src/Test2.cpp
src/Test3.cpp
)
target_include_directories(${THIS} PRIVATE include)
target_link_libraries(${THIS} PUBLIC lib1 lib2 lib3
endif()

Basically you can build each test as an object library, make an executable from each of that and then another one using all of them as follows:
# add object library for each file
add_library(UTest1_obj OBJECT)
target_sources(UTest1_obj PRIVATE src/Test1.cpp)
target_include_directories(UTest1_obj PRIVATE include)
[...]
# add executables linking one object each
add_executable(UTest1)
target_link_libraries(UTest1 PRIVATE UTest1_obj)
[...]
# add an executable with all objects
add_executable(UTestAll)
target_link_libraries(UTestAll PRIVATE UTest1_obj UTest2_obj UTest3_obj)
It may make sense to take a look at CMake macros, so it isn't that repetetive.
It may also make sense to look into ctest and some testing framework (GoogleTest, Catch2 or something like that)

Related

How to avoid numerous builds of shared dependency in cmake

my team has a utility library, lets call it Utils which is built with CMake.
it is ExternalProject_Added in another library's CMake, let's call it A.
our executable, App, ExternalProject_Adds A, and Utils.
out problem is that utils is built twice, once when A is built and again when App is built when it could have been built only once.
Here are examplary CMake files:
Utils:
cmake_minimum_required(VERSION 3.16.3)
project(Utils)
include_directories(${PROJECT_SOURCE_DIR}/Inc)
file(GLOB SOURCE_FILES ${PROJECT_SOURCE_DIR}/Src/*.cpp)
add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES})
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX})
A:
cmake_minimum_required(VERSION 3.16.3)
project(A)
include_directories(${PROJECT_SOURCE_DIR}/Inc)
file(GLOB SOURCE_FILES ${PROJECT_SOURCE_DIR}/Src/*.cpp)
add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES})
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX})
include(ExternalProject)
target_link_libraries(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/Lib/libUtils.a)
ExternalProject_Add(Utils
GIT_REPOSITORY ${SOME_GIT_URL}/_git/Utils
GIT_TAG master
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PROJECT_SOURCE_DIR}/Lib
)
ExternalProject_Get_Property(Utils SOURCE_DIR)
target_include_directories(${PROJECT_NAME} PRIVATE ${SOURCE_DIR}/Inc/)
add_dependencies(${PROJECT_NAME} Utils)
App:
cmake_minimum_required(VERSION 3.16.3)
project(App)
include_directories(${PROJECT_SOURCE_DIR}/Inc)
file(GLOB SOURCE_FILES ${PROJECT_SOURCE_DIR}/Src/*.cpp)
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX})
include(ExternalProject)
target_link_libraries(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/Lib/libUtils.a)
ExternalProject_Add(Utils
GIT_REPOSITORY ${SOME_GIT_URL}/_git/Utils
GIT_TAG master
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PROJECT_SOURCE_DIR}/Lib
)
ExternalProject_Get_Property(Utils SOURCE_DIR)
target_include_directories(${PROJECT_NAME} PRIVATE ${SOURCE_DIR}/Inc/)
add_dependencies(${PROJECT_NAME} Utils)
target_link_libraries(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/Lib/libA.a)
ExternalProject_Add(A
GIT_REPOSITORY ${SOME_GIT_URL}/_git/A
GIT_TAG master
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PROJECT_SOURCE_DIR}/Lib
)
ExternalProject_Get_Property(A SOURCE_DIR)
target_include_directories(${PROJECT_NAME} PRIVATE ${SOURCE_DIR}/Inc/)
add_dependencies(${PROJECT_NAME} A)
how can we achieve the wanted result?
ExternalProject_Add fetches, builds and installs a separate project. It supports invoking many different build systems: Makefiles, Automake, ninja, CMake, and pretty much anything else too if you supply it the appropriate build commands. This means, however, that ExternalProject_Add has no real way of "communicating" with the build system it's invoking. It simply invokes it, waits for it to run, and then provides the installed files (binaries, headers, etc) to the main project being built.
In your case, this means that when "A" is built, a separate instance of CMake is launched to build it. Once this separate instance has finished running, the "A" target is considered to be built, and "libA.a" can be used. The targets defined within the build of "A" are not visible to the build of "App" - they're separate CMake builds and simply don't share their targets. Therefore, the version of "Utils" that "A" has built is not immediately visible to "App".
There is a way to make this work, though: Simply make "A" install the "Utils" library that it builds. In the end, the build and installation of "A" should place both libA.a and libUtils.a in the target install directory (i.e. ${CMAKE_INSTALL_PREFIX}/lib). It must also place the necessary headers in that install directory (i.e. in ${CMAKE_INSTALL_PREFIX}/include to keep with conventions).
In general, you also shouldn't put the install directories of subprojects into the project's source tree. Instead, put them at ${CMAKE_CURRENT_BINARY_DIR}/some_subdirectory. Once the library is built, you can copy it to the install directory (with the install command) if you want the library to be visible to users of the project.
Here's a rough (incomplete) outline of what the build script of "A" will have to do:
cmake_minimum_required(VERSION 3.16.3)
project(A)
include_directories(${PROJECT_SOURCE_DIR}/Inc)
file(GLOB SOURCE_FILES "${PROJECT_SOURCE_DIR}/Src/*.cpp")
add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES})
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX}/Lib)
# TODO: Also install headers of A to ${CMAKE_INSTALL_PREFIX}/Inc
include(ExternalProject)
ExternalProject_Add(Utils
GIT_REPOSITORY ${SOME_GIT_URL}/_git/Utils
GIT_TAG master
INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/Utils_install
)
ExternalProject_Get_Property(Utils INSTALL_DIR)
target_include_directories(${PROJECT_NAME} PRIVATE ${INSTALL_DIR}/Inc/)
target_link_libraries(${PROJECT_NAME} ${INSTALL_DIR}/Lib/libUtils.a)
add_dependencies(${PROJECT_NAME} Utils)
# TODO: Install libraries from ${INSTALL_DIR}/Lib to ${CMAKE_INSTALL_PREFIX}/Lib
# TODO: Install headers from ${INSTALL_DIR}/Inc to ${CMAKE_INSTALL_PREFIX}/Inc
"Utils" will similarly have to install both its binary and its headers. (The example script assumes that "Utils" places its binary in ${CMAKE_INSTALL_PREFIX}/Lib and its headers in ${CMAKE_INSTALL_PREFIX}/Include, just like "A".)
Once you've changed the build scripts of "Utils" and "A" like that, "App" only has to build "A" and can use the copy of "Utils" that "A" provides.

Deplying a C++ application on Linux- linking everything statically to simplify deployment?

I am building a C++ project from Github and want to deploy the code to a remote Linux machine. This is all new to me.
The project has a main.cpp, which includes the various headers/sources like a library.
The CMake outputs an executable (to represent main.cpp) AND a separate static library. The project also uses OpenSSL, which I have linked statically.
I presume the OpenSSL functions are included within the static library? So when I deploy, I don't need to copy-over or install any OpenSSL on the remote machine?
Is it possible to modify the CMake so the application and the library are merged in to one file?
I am trying to make deployment as simple as copying over a single file, if this is possible.
Any additional advice/references are most-welcome.
UPDATE the CMake script:
cmake_minimum_required(VERSION 3.20)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}")
project(helloworld C CXX)
set (CMAKE_CXX_STANDARD 20)
set (CMAKE_BUILD_TYPE Release)
set (BUILD_MAIN TRUE)
set (BUILD_SHARED_LIBS FALSE)
set (OPENSSL_USE_STATIC_LIBS TRUE)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set( HELLOWORLD_HEADERS helloworld/File1.h helloworld/File2.h )
set( HELLOWORLD_SOURCES helloworld/File1.cpp helloworld/File2.cpp )
# Static library
add_library( helloworld ${HELLOWORLD_SOURCES} ${HELLOWORLD_HEADERS} )
# Rapidjson
include_directories(/tmp/rapidjson/include/)
# OpenSSL
if (NOT OPENSSL_FOUND)
find_package(OpenSSL REQUIRED)
endif()
add_definitions(${OPENSSL_DEFINITIONS})
target_include_directories(helloworld PUBLIC $<BUILD_INTERFACE:${OPENSSL_INCLUDE_DIR}>)
target_link_libraries(helloworld PRIVATE ${OPENSSL_LIBRARIES})
set( HELLOWORLD_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR})
include(GNUInstallDirs)
target_include_directories(helloworld PUBLIC
$<BUILD_INTERFACE:${HELLOWORLD_INCLUDE_DIRS}/>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/helloworld>
)
set_target_properties(helloworld PROPERTIES PUBLIC_HEADER "${HELLOWORLD_HEADERS}")
add_library(helloworld::helloworld ALIAS helloworld)
option(HELLOWORLD_INSTALL "Install HelloWorld" TRUE)
if (HELLOWORLD_INSTALL)
install(TARGETS helloworld
EXPORT helloworld
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/helloworld/
)
configure_file("${CMAKE_CURRENT_LIST_DIR}/helloworld-config.cmake.in" "${CMAKE_BINARY_DIR}/helloworld-config.cmake" #ONLY)
install(FILES "${CMAKE_BINARY_DIR}/helloworld-config.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/helloworld")
install(EXPORT helloworld
FILE helloworld-targets.cmake
NAMESPACE helloworld::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/helloworld
)
endif()
if (BUILD_MAIN)
add_executable(main main.cpp)
target_link_libraries(main helloworld)
endif()
ITNOA
I it is very helpful to make URL of your GitHub's project, but I write some public notes about that
In generally in CMake for static linking your library to your executable, you can write simple like below (from official CMake example)
add_library(archive archive.cpp zip.cpp lzma.cpp)
add_executable(zipapp zipapp.cpp)
target_link_libraries(zipapp archive)
In above example your executable file is just work without needing .a library file and you can simple copy single file.
if you want to make all of thing static, you make sure all dependencies make static link to your project, like CMake: how to produce binaries "as static as possible"
if you want to prevent library creation, Probably in your CMake file, you can find add_library command, and add_executable command. you can remove add_library command and add all sources to add_executable command.
for example add_executable(a.out main.cpp lib.cpp)

CMake split target_sources in CMakeLists.txt into different targets

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})

CMake - How to make private headers of a library available to its tests?

So, I'm making part of a project a library with some headers that are the interface to the library, and the remaining are private to the library itself. So for my library the CMAKE part looks like:
add_library(${PROJECT_NAME} ${PROJECT_SOURCES} "${PROJECT_BINARY_DIR}/libversion.h")
add_library(my::lib ALIAS ${PROJECT_NAME})
target_include_directories(${PROJECT_NAME}
PRIVATE ${Boost_INCLUDE_DIRS}
PRIVATE ${PROJECT_BINARY_DIR} #to locate libversion.h
INTERFACE ${PUBLIC_INCLUDE_HEADERS}
)
And then my test target:
add_executable(${TEST_NAME} ${TEST_SOURCES})
add_test(NAME LibTest COMMAND ${TEST_NAME})
target_link_libraries(${TEST_NAME}
PRIVATE ${Boost_LIBRARIES}
PRIVATE my::lib
)
But this only allows me to test my public interface. If I want to unit test my library, how would I go about declaring the access to the remaining headers in project lib? The way I see it would be to add an entire new target my::lib::testing which declares the interface as the current source directory (where all headers currently are located, seperating public from private headers is another issue I've yet to handle). So something like this:
add_library(${PROJECT_NAME}_TESTING ${PROJECT_SOURCES} "${PROJECT_BINARY_DIR}/libversion.h")
add_library(my::lib::testing ALIAS ${PROJECT_NAME}_TESTING)
target_include_directories(${PROJECT_NAME}_TESTING
PRIVATE ${Boost_INCLUDE_DIRS}
PRIVATE ${PROJECT_BINARY_DIR} #to locate libversion.h
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
But this requires two different targets to be build depending on the usage. One for my application linking to alias my::lib and one for unit testing, linking alias my::lib::testing.
So my question is, how do I cleanly separate headers so I can have only my INTERFACE headers shown by targets, but still access the remaining headers by my test target?
Both PRIVATE and PUBLIC items populate the INCLUDE_DIRECTORIES property of an target, so you can try to use it in target_include_directories for the test project.
add_executable(${TEST_NAME} ${TEST_SOURCES})
add_test(NAME LibTest COMMAND ${TEST_NAME})
target_link_libraries(${TEST_NAME}
PRIVATE ${Boost_LIBRARIES}
PRIVATE my::lib
)
target_include_directories( ${TEST_NAME} PRIVATE $<TARGET_PROPERTY:my::lib,INCLUDE_DIRECTORIES>)

Right way of creating a library, installing it and linking to another project using CMake

I have a set of files that I want to make into a library and then use that library in another project. This it how it looks like right now
cmake_minimum_required(VERSION 2.8)
aux_source_directory(. SRC_LIST)
SET(CMAKE_CXX_FLAGS "-std=c++11 -fopenmp -fPIC")
add_library (helperlibs lib1.cpp lib2.cpp lib3.cpp lib4.cpp )
INSTALL(TARGETS helperlibs
DESTINATION "${HOME}/lib"
)
INSTALL(FILES lib1.h lib2.h lib3.h lib4.h helperheader.h
DESTINATION "${HOME}/include/helperlibs"
)
In this code Lib4 depends on Lib1-3 and Lib3 depends on Lib1-2 and Lib2 depends on Lib1. Each of these cpp files also depend on a helperheader.h file that contains some definitions and structs.
In my project I have the following CMake file
cmake_minimum_required(VERSION 2.8)
aux_source_directory(. SRC_LIST)
SET( CMAKE_CXX_FLAGS "-std=c++11 -fopenmp -fPIC")
SET(MYINCS ${HOME}/include/helperlibs)
SET(MYLIBDIR ${HOME}/lib)
SET(MYLIBS ${MYLIBDIR}/libhelperlibs.a )
include_directories(${MYINCS})
add_executable(main main.cpp)
target_link_libraries(main ${MYLIBS})
So what I am wondering if you want to create a static library and link to from a another project using cmake is this the way you should write?
Embed the search paths into the library target as properties and create an export.
This way executables in the same build tree will find the library and its include files without you having to specify paths (they become implicit).
I needed to read the cmake documentation carefully a few times before it dawned on me how it should work.
http://www.cmake.org/cmake/help/v3.0/manual/cmake-packages.7.html#creating-packages
http://www.cmake.org/cmake/help/v3.0/manual/cmake-buildsystem.7.html
excerpt from a live example:
add_library(trustportal-util ${CMAKE_CURRENT_LIST_FILE} ${_source_files} ${_disabled_source_files} )
target_link_libraries(trustportal-util ${Boost_LIBRARIES})
if(APPLE)
find_library(SECURITY_FRAMEWORK Security)
target_link_libraries(trustportal-util ${SECURITY_FRAMEWORK})
else()
find_library(LIB_SSL ssl)
find_library(LIB_CRYPTO crypto)
target_link_libraries(trustportal-util ${LIB_SSL} ${LIB_CRYPTO})
endif()
target_compile_definitions(trustportal-util PUBLIC BOOST_MOVE_USE_STANDARD_LIBRARY_MOVE)
target_include_directories(trustportal-util PUBLIC ${Boost_INCLUDE_DIRS})
target_include_directories(trustportal-util PRIVATE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
target_include_directories(trustportal-util SYSTEM PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
install(TARGETS trustportal-util
EXPORT trustportal-utilExport
DESTINATION lib
INCLUDES DESTINATION include
)
INSTALL(EXPORT trustportal-utilExport DESTINATION lib)
One option is to do what you are currently doing where you place the includes and libs in a common location, perhaps /usr/include and /usr/lib on linux, or ${HOME} on both Windows/Linux, up to you.
Another option is available if you use git. You can include the project inside another using submodules. Then use include_directory(${submodule}) and build and link directly for every project. The advantage of this approach is that dependencies are more localised. One problem with this method is if you recursively do this, you may end up with duplicates of some projects and cmake will complain that two libraries have the same name.