Dominik Berner

C++ Coder, Agilist, Rock Climber


Project maintained by bernedom Read the privacy statement for this blog

Using Conan as a CMake Dependency Provider

Using Conan as a CMake Dependency Provider

Managing dependencies in CMake is hard. It’s a common pain point for C++ developers, especially when working on multi-platform projects or with complex dependencies. The introduction of dependency providers in CMake 3.24 aims to simplify this process by allowing package managers like Conan to provide dependency information directly to CMake. Conan and CMake are already a powerful combination for managing C++ dependencies, and this new feature further enhances their integration. In this post, we’ll explore how to use Conan as a CMake dependency provider, making dependency management in CMake projects more seamless and efficient. A sample project can be found on my github account

CMake Dependency Providers in a nutshell

Dependency providers are a new feature introduced in CMake 3.24 that allows package managers to provide dependency information directly to CMake. This information includes the location of the libraries, their include directories, and other necessary information for building the project. When a dependency provider is registered in CMake each call to find_package or FetchContent_MakeAvailable will trigger the corresponding function in the dependency provider script.

CMake Best Practices - The book

CMake Best Practices: Discover proven techniques for creating and maintaining programming projects with CMake. Learn how to use CMake to maximum efficiency with this compendium of best practices for a lot of common tasks when building C++ software.

Behind the curtains, a dependency provider is a CMake script that provides functions to locate dependencies through whatever means it can. In the case of Conan, this means calling calling conan install with the appropriate toolchain configuration and then extracting the necessary information from the files generated by Conan. To install a depenndency provider the script containing the provider definition is passed using the CMAKE_PROJECT_TOP_LEVEL_INCLUDES variable either from the command line or through a CMake preset The official CMake documentation provides an in-depth explanation of how dependency providers work and how to create your own.

Setting up Conan as a CMake Dependency Provider

To set up Conan as a dependency provider we need three things:

  • CMake 3.24 or later
  • Conan 2.0.2 or later
  • The Conan CMake helper script to install it as a dependency provider

Installing CMake and Conan is straightforward and well-documented, so we won’t cover it here. The Conan CMake helper script is a CMake script that sets up Conan as a dependency provider in your project can be found in the cmake-conan repository. As of May 2024 the support is still experimental, but it should be stable enough for most use cases. To use the Conan CMake helper script, you can either download it manually or use it as a git submodule in your project. Since dependency providers have to be configured at the very beginning of the CMake configuration process, getting it over FetchContent or similar is not an option.

I usually end up with a directory structure like this:

├── cmake-conan # git submodule
│   ├── conan_provider.cmake
│   └── ...
├── CMakeLists.txt
├── conanfile.txt
└── src
    └── ...

The conan_provider.cmake script is the Conan CMake helper script that we will use to set up Conan as a dependency provider. The conanfile.txt file is the Conan configuration file that lists the dependencies of the project. The src directory contains the source code of the project and the CMakeLists.txt file contains the main CMake configuration.

Preparing the Conan and CMake configuration

Let’s assume we are building a simple project that depends on the fmt library for string formatting, which is available from the conan center repository. One of the nice things about dependency providers is that the CMakeLists.txt does not need any special configuration and can just use find_package and target_link_libraries as usual. The Conan CMake helper script will take care of the rest. The CMakeLists.txt file for the project looks like this:

cmake_minimum_required(VERSION 3.24)

project(
    hello_world
    LANGUAGES CXX
)

find_package(fmt 10.2.1 REQUIRED)
add_executable(hello src/main.cpp)
target_link_libraries(
    hello
    PRIVATE fmt::fmt
)

Next, The conanfile.txt file lists the dependencies of the project:

[requires]
fmt/10.2.1

[generators]
CMakeDeps

Using the CMakeDeps generator in the conanfile.txt file tells Conan to generate the necessary information for CMake’s find_package function. This is necessary for the Conan CMake helper script to work correctly. There is also the CMakeToolchain generator that generates a toolchain file for CMake, but this is not recommended when using Conan as a dependency provider.

With that in place, we can now set up Conan as a dependency provider in the CMakeLists.txt file:

Using Conan as a CMake Dependency Provider

To set up Conan as a dependency provider, we need to pass the conan_provider.cmake script to CMake using the CMAKE_PROJECT_TOP_LEVEL_INCLUDES variable. This can be done either from the command line or through a CMake preset. The conan_provider.cmake script is the Conan CMake helper script that sets up Conan as a dependency provider.

cmake -S . -B build -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./cmake-conan/conan_provider.cmake -DCMAKE_BUILD_TYPE=Debug

Note that Conan requires the CMAKE_BUILD_TYPE to be set in order to download or build the correct version of the dependencies. The conan_provider.cmake script will take care of setting up the necessary Conan profiles and installing the dependencies in the local cache. The binaries and include files for the dependencies will be placed in the local Conan cache, which is usually located in the user’s home directory. Only the configuration files generated by Conan will be placed in the build directory. By default the Conan helper script is configured to use the default Conan profile and it tries to build any missing dependencies from source. These settings can be changed by setting the CONAN_BUILD_PROFILE and CONAN_INSTALL_ARGS variables respectively.

After running CMake, the project can be built as usual:

cmake --build build

And with that the project the project is set up to use Conan as a dependency provider. I recommend to use CMake presets to make the configuration more reproducible and to avoid having to remember the command line arguments, especially when working with multiple configurations or platforms. For public projects I generally include the Conan helper script as a git submodule but depending on the project it might be easier to just download it manually.

Is it really that easy?

The introduction of CMakes dependency providers closes a long-existing gap in CMake’s dependency management and like CMake Presets I consider them a vast improvement regarding the tooling situation around C++ projects. So far I have only used Conan as a dependency provider but I am looking forward to seeing how other package managers will integrate with CMake. What I particularly like is that the CMake configuration can remain simple and platform agnostic while the complex dependency management is handled by Conan. This makes it easier to switch between different build systems or to integrate the project into a larger build system and in my opinion is a great incentive to use Conan for C++ projects.

Written on May 24, 2024