Contents

I've tried CMake with Conan and I really liked it

As it’s probably obvious by now, I’m not a fan of CMake. I don’t like the weird syntax, the verbosity of the language, the flawed concept of multistage build system generation and external dependency management which seems like an after thought.

I can’t deny though, that CMake is very popular and has a lot of traction so, it’s the necessary evil until we all, as an industry, decide on some other solution.

Recently, I decided to give conan a go in one of my CMake projects and I must say that I was positively surprised.

What’s Conan?

Conan is a package manager which solves a lot of CMake’s problems. Without conan (or any other package manager for that matter) preparing a self-contained development environment with CMake feels like a hack. With it, dependency management feels a bit more streamlined and less painful.

How does it work?

In short, you specify your dependencies in conanfile.txt (or conanfile.py - for more complex configurations). Prior to building your project, conan will install all the dependencies and prepare the environment for CMake so, it can find all the packages without any problems at all.

Example

Let’s suppose you’re writing a program requiring e.g. fmt.

1
2
3
4
5
6
#include <fmt/core.h>

int main() {
  fmt::print("Hello, world!\n");
  return 0;
}

No problems at all! You go to conan center and there’s the recipe:

1
2
3
4
5
6
7
8
9
[requires]
fmt/10.2.1

[generators]
CMakeDeps
CMakeToolchain

[layout]
cmake_layout

Save it as conanfile.txt. In CMakeLists.txt you just have to find the package. Here’s an example CMakeLists.txt in its entirety:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
cmake_minimum_required(VERSION 3.12)
project(fmtproj LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(fmt REQUIRED)

add_executable(fmtproj fmtproj.cpp)

target_link_libraries(fmtproj PRIVATE fmt::fmt)

You don’t even have to read Conan’s documentation to use the recipe, because conan center literally gives you all the commands that you have to invoke to use it. So, let’s do that:

conan install -of bld --build=missing .

Once it builds and caches fmt, conan even tells you how to use your dependencies and start the CMake build:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
======== Finalizing install (deploy, generators) ========
conanfile.txt: Writing generators to /home/tomasz/fmtproj/bld/build/Release/generators
conanfile.txt: Generator 'CMakeDeps' calling 'generate()'
conanfile.txt: CMakeDeps necessary find_package() and targets for your CMakeLists.txt
    find_package(fmt)
    target_link_libraries(... fmt::fmt)
conanfile.txt: Generator 'CMakeToolchain' calling 'generate()'
conanfile.txt: CMakeToolchain generated: conan_toolchain.cmake
conanfile.txt: Preset 'conan-release' added to CMakePresets.json. Invoke it manually using 'cmake --preset conan-release' if using CMake>=3.23
conanfile.txt: If your CMake version is not compatible with CMakePresets (<3.23) call cmake like: 'cmake <path> -G "Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=/home/tomasz/fmtproj/bld/build/Release/generators/conan_toolchain.cmake -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_BUILD_TYPE=Release'
...

CMake can be invoked either using presets or the ’traditional’ way. With presets, it’s super easy:

cmake --preset conan-release
cmake --build --preset conan-release

Without presets, it’s a bit more verbose

cmake \
    -Bbld/build/Release/generators \
    -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake \
    -GNinja \
    -S .

ninja -C bld/build/Release/generators

Just like that all dependencies have been satisfied and integrated into the project.

Integration with meson

conan can cater not only to CMake projects but meson as well (although meson has its own ‘wraps’ system which is very convenient on its own). It’s purely a matter of choosing a different generator. In case of meson, the dependencies are resolved with pkg-config. Prior example built with meson would need the below, adjusted conanfile.txt:

1
2
3
4
5
6
[requires]
fmt/10.2.1

[generators]
PkgConfigDeps
MesonToolchain

In meson.build you just need to declare the dependencies to find them:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
project('fmtproj', 'cpp',
  version : '0.1',
  default_options : ['warning_level=3', 'cpp_std=c++14'])

fmt_dep = dependency('fmt', version : '>=10.2.1')

executable('fmtproj',
           'fmtproj.cpp',
           install : true,
           dependencies : [fmt_dep])

Build directory setup is done in exactly the same way as before:

conan install -of bld --build=missing .

Meson bootstrap will need the native-file to setup the paths properly:

meson setup --native-file bld/conan_meson_native.ini bld

After that, the build can be done as usual:

meson compile -C bld

libboxes

I’ve actually started working on a library containing all data structures I find myself writing very frequently in variety of projects and thought that it would be good to finally collate all of these (even only as a reference implementation) in a working, ready to use library.

The library is called libboxes and since I want to appeal to a wider audience and provide flexibility for myself as well, CMake integration is a must hence I decided to provide both meson and CMake as build systems. The library contains conan recipes so, it’s super easy to use it in CMake projects. It’s still pretty much a work in progress and at the moment, the selection of available data structures is limited but I plan to extend it to offer a greater variety.

This was my main motivation to finally explore conan and try it out on my own and with full honesty I’m glad I did.