I have the following folder structure for my cmake project. My app is dependent on curl
. I also have a tests folder in which I perform some testing on the curl
library. I have access to the curl
header files in myapp
but my IDE complains about #include <curl/curl.h
not found in the source code curl_gtests.cpp
.
I read online that the following folder structure is a good structure especially since we have a separate tests
folder that utilizes the built libraries and executables and it makes it clean and organized. However, I have difficulty creating access to the tests
folder to those libraries/executables that are external or developed within myapp
myapp
│ CMakeLists.txt
│
├───data
│ data.csv
│
├───external
│ └───curl
│ CMakeLists.txt
│
├───include
│ myapp.h
│
├───out
├───src
│ main.cpp
│
└───tests
CMakeLists.txt
curl_gtests.cpp
The content of the top level CMakeLists.txt
is as follows:
cmake_minimum_required(VERSION 3.24)
project(myapp VERSION 1.0.0)
include(FetchContent)
include(CMakePrintHelpers)
cmake_print_variables(CMAKE_CXX_FLAGS)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# Enable testing
enable_testing()
add_subdirectory(external/curl)
add_subdirectory(tests)
add_executable(myapp)
target_sources(myapp PRIVATE src/main.cpp)
target_include_directories(myapp PUBLIC include)
target_link_libraries(trade-analytica-app PUBLIC CURL::mylibcurl)
The content of CMakeLists.txt
for the curl
library is as follows:
cmake_minimum_required(VERSION 3.24)
project(libcurl LANGUAGES CXX VERSION 1.0.0)
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
set(WIN32 ON)
set(MACOS OFF)
set(LINUX OFF)
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(WIN32 OFF)
set(MACOS ON)
set(LINUX OFF)
else()
set(WIN32 OFF)
set(MACOS OFF)
set(LINUX ON)
endif()
include(FetchContent)
include(CMakePrintHelpers)
if (WIN32)
set(CMAKE_USE_SCHANNEL ON)
endif()
FetchContent_Declare(curl
URL https://github.com/curl/curl/releases/download/curl-7_75_0/curl-7.75.0.tar.xz
URL_HASH SHA256=fe0c49d8468249000bda75bcfdf9e30ff7e9a86d35f1a21f428d79c389d55675 # the file hash for curl-7.75.0.tar.xz
USES_TERMINAL_DOWNLOAD TRUE)
FetchContent_MakeAvailable(curl)
add_library(curl_int INTERFACE)
target_include_directories(curl_int INTERFACE
$<BUILD_INTERFACE:${curl_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include/curl> # <prefix>/include/mylib
)
target_link_libraries(curl_int INTERFACE libcurl)
add_library(CURL::mylibcurl ALIAS curl_int)
get_target_property(curl_int_dir CURL::mylibcurl INTERFACE_INCLUDE_DIRECTORIES)
and finally for the CMakeLists.txt
in the tests
folder:
cmake_minimum_required(VERSION 3.24)
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
set(WIN32 ON)
set(MACOS OFF)
set(LINUX OFF)
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(WIN32 OFF)
set(MACOS ON)
set(LINUX OFF)
else()
set(WIN32 OFF)
set(MACOS OFF)
set(LINUX ON)
endif()
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
)
if(WIN32)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
endif()
FetchContent_MakeAvailable(googletest)
add_executable(
hello_test
curl_gtests.cpp
)
target_link_libraries(
hello_test
GTest::gtest_main
)
include(GoogleTest)
gtest_discover_tests(hello_test)
I read online that the following folder structure is a good structure (...)
Your project layout tree loosely resembles the The Pitchfork Layout (PFL) specification.
Regarding your problem, if you want to cover components of your project with unit tests, you need to break your projects into components that can be consumed by the unit test targets. By the looks of your project, you haven't done yet.
Here's the relevant section from your ./CMakeLists.txt
:
(...)
# Enable testing
enable_testing()
add_subdirectory(external/curl)
add_subdirectory(tests)
add_executable(myapp)
(...)
It looks like your project does not specify any library target, and only specifies an executable target: myapp
.
This means you provided CMake with no good way to make your code available to testing, because it has no build artifact (library, module) that makes any component available as a target for other targets such as your unit tests.
Your unit test targets also do not list any other target as a dependency. Here's the contents of your ./tests/CMakeLists.txt
:
(...)
add_executable(
hello_test
curl_gtests.cpp
)
target_link_libraries(
hello_test
GTest::gtest_main
)
(...)
In the call above, you create the CMake target hello_test
and then set GTest::gtest_main
as one of its dependencies, but it does not include anything else. If you want your tests to access a component in your code, you need to include its CMake target in target_link_libraries
along with GTest::gtest_main
.
What you need to do is to peel out the bits you want to cover with tests into an independent component (i.e., a library, and in your case either a static or module library), and then pass its CMake target as a dependency of the unit test target.