Cmake, Conan, and GTest Example

Source Code: https://github.com/jroddev/basic-conan-cmake-gtest-project

This sample project is what I would consider a minimal example of a project that uses cmake, conan, and gtest. I make use of the very handy cmake-conan wrapper. This setup also works seamlessly in CLion (using cmake directly, not the conan plugin).

.
|_ cmake
|	|_ conan.cmake
|_ app
|   |_ include
|   |_ src
|   |_ test
|   |	|_ CMakeList.txt
|   |_ CmakeList.txt
|_ CmakeList.txt
|_ conanfile.txt
project structure

First thing you'll probably notice is there are quite a few cmake files.

  • ./CMakeList.txt: top-level cmake file, includes the others
  • ./cmake/conan.cmake: Setup cmake-conan wrapper
  • ./app/CMakeList.txt: Handles building of the app module
  • ./app/test/CMakeList.txt: Sets up test executable and adds it to CTest

cmake-conan allows you to configure conan directly in cmake instead of requiring a conanfile.txt. However in order to get this working I would have needed to move code out of conan.cmake and into the root CMakeList.txt. Because I wanted to keep the cmake-conan details hidden I decided to move all the cmake code into the conan.cmake file and to have a conanfile.txt at the root.

Top Level CMakeList.txt

cmake_minimum_required(VERSION 3.22)
project(conan_project)

set(CMAKE_CXX_STANDARD 20)

include(cmake/conan.cmake)

option(RUN_TESTS "Build the tests" ON)
if(RUN_TESTS)
    enable_testing()
    find_package(GTest)
endif()

add_subdirectory(app)
./CMakeList.txt

The job of this file is some basic boiler plate and then to include other cmake files. At the end I include the 'app' module using add_subdirectory.

CMake Conan Helper File


list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR})
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR})

if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
    message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
    file(DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/v0.16.1/conan.cmake"
            "${CMAKE_BINARY_DIR}/conan.cmake"
            EXPECTED_HASH SHA256=396e16d0f5eabdc6a14afddbcfff62a54a7ee75c6da23f32f7a31bc85db23484
            TLS_VERIFY ON)
endif()

include(${CMAKE_BINARY_DIR}/conan.cmake)

conan_cmake_autodetect(settings)
conan_cmake_run(CONANFILE ${CMAKE_CURRENT_SOURCE_DIR}/conanfile.txt
        BASIC_SETUP CMAKE_TARGETS
        BUILD missing
        REMOTE conancenter
        SETTINGS ${settings}
        )
./cmake/conan.cmake

Most of this is copy and pasted from the cmake-conan README.md. One modification I made was to opt for a conanfile.txt file at the project root directory.

Conanfile

[requires]
gtest/1.10.0
spdlog/1.9.2

[generators]
cmake_find_package
conanfile.txt

App Module

add_executable(conan_project src/main.cpp)

find_package(spdlog)
target_include_directories(conan_project PRIVATE
        include
        ${spdlog_INCLUDE_DIR}
        )
target_link_libraries(conan_project spdlog::spdlog)
set(app_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})

if(RUN_TESTS)
    add_subdirectory(test)
endif()
./app/CMakeList.txt

This is the file most likely to grow with the project. I find, include, and link the spdlog library. I found spdlog_INCLUDE_DIR and spdlog::spdlog by looking in the cmake generated Findspdlog.cmake file. The cmake-conan wrapper will create a Find<dep>.cmake file for each dependency. You can use these files to see what variables are set and what targets are exported.

App Test Module

add_executable(conan_project_tests main.cpp)
target_include_directories(conan_project_tests PRIVATE ${app_SOURCE_DIR}/include)
target_link_libraries(conan_project_tests GTest::gtest_main)

add_test(NAME conan_project_tests
        COMMAND conan_project_tests)
set_tests_properties(${conan_project_tests} PROPERTIES TIMEOUT 10)
./app/test/CMakeList.txt

This file creates, compiles and links the test executable. add_test is used to register this test suite with CTest.

Build and Test

If you're using cmake you can just run the executable and tests from the IDE.

If you're building from the commandline the the .github/workflows/cmake.yml pipeline has a working example. Here is a cleaned, simpler version:

# configure
cmake -B build -DCMAKE_BUILD_TYPE=Release

# build
cmake --build ./build/ --config Release

# run
 ./build/bin/conan_project

# test
ctest --test-dir build/
build and test