Using Conan in a non-intrusive way in CMake-based projects

Kai Wolf

Kai Wolf 13 February 2019

Using Conan in a non-intrusive way in CMake-based projects

Ever since Software is eating the world an ongoing debate in the C and C++ development world is about proper dependency management and if a large amount of thirdparty dependencies are actually good or bad for the long-term maintainability of a project. Relating to C and C++ development, this is particularly interesting, because this doesn’t seem to be an issue in many other programming languages.

For instance, if we look at the latest working model in frontend web development, it is not uncommon for a project to have dozens or even hundreds of dependencies. However, this is usually not an issue, since all those dependencies are managed using a dedicated package manager and get resolved automatically.

NodeJS, a commonly used web server written in JavaScript for example, uses a package manager called NPM. Ruby developers may use RubyGems, a package management framework for Ruby packages. Even relatively old languages such as PHP have something like Composer, a package management system for PHP packages.

On the other hand, maintaining a large chain of dependencies in a C or C++ based project can be quite cumbersome. In rare cases, it may even lead to software teams treating dependencies as enemies, which have to be deleted. For instance, it is told that the Excel team at Microsoft once aggressively neglected all dependencies to their product. However, unlike in the web development business, a proper package management in C or C++ is very complicated for a variety of reasons.

Often times, thirdparty libraries have to be treated specially: May be a custom configuration or only specific parts of a library is needed. Moreover, sometimes libraries need to be compiled with a specific compiler for ABI compatibility reasons, before they can be used on a software project. Not to mention, partially linking where no library files but the intermediate object files are needed for a consuming project. The different possible use cases seem to be endless here.

Introducing Conan as a dependency manager

A promising contender to tackle package dependency management is Conan, the successor of biicode. Conan is a decentralized, build system agnostic, package manager with a client-server architecture, where you define your dependencies (requirements in Conan-speak) in a dedicated configuration file and put it alongside your source code. Conan then needs to be called from the command line (once), to fetch all your dependencies either as pre-compiled binary artifacts or in source form and caches them locally. As Conan uses a client-server architecture, binary artifacts can be stored on a dedicated server, to further reduce the build times.

Using CMake to manage the build process of a software project, we don’t want our package dependency manager to interfere with our build system, since they are two different responsibilities and we should strive for a separation of concerns here. Unfortunately, when using Conan in its latest version, this still is not achievable. According to their documentation, the preferred way of integrating Conan is to include a pre-populated conanbuildinfo.cmake right into your build system.

include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()

add_executable(timer timer.cpp)
target_link_libraries(timer ${CONAN_LIBS})

However, as pointed out above, we don’t want to mix two different responsibilities here: For one, we don’t want to lock-in into a specific tool for managing our dependencies and we certainly don’t want to adjust our existing configuration to fulfill the requirements of another build tool. Furthermore, we don’t want to burden any clients using our software project to now add Conan as their package management tool as well.

Using cmake_paths generators

Another way of integrating Conan is to use the cmake_paths generator. This way, we don’t need to touch our original build system configuration but let Conan produce a toolchain file that we can inject from outside as a parameter into our build system (as opposed to the conanbuildinfo.cmake, which needs to be referenced right in the configuration file). Unfortunately, in its current state this will fail for even simple cases, if the dependency itself has other dependencies (so called transitive dependencies).

# CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
project(HelloConanOpenCV)

find_package(OpenCV REQUIRED)

add_executable(hello_opencv hello.cc)
target_include_directories(hello_opencv PRIVATE ${OpenCV_INCLUDE_DIRS})
target_link_libraries(hello_opencv ${OpenCV_LIBS})

# conanfile.txt
[requires]
opencv/4.0.1@conan/stable

[generators]
cmake_paths

If we try to use this now, first calling Conan and then CMake to build our project, this will fail as the OpenCV dependency has been populated with CONAN_* variables that are unknown to our build system. Hence, in its current state, we are forced to populate those variables before calling our build system, which is prone to errors and unacceptable.

$ mkdir build && cd build
$ conan install --build=missing ..
$ cmake \
    -DCMAKE_TOOLCHAIN_FILE=conan_paths.cmake \
    -DCMAKE_BUILD_TYPE=Release \
    -DCONAN_LIBJPEG_ROOT=.... \
    -DCONAN_ZLIB_ROOT=.... \
    -DCONAN_LIBPNG_ROOT=.... \
    -DCONAN_LIBTIFF_ROOT=.... \

As Conan is developed in public and Open-Source, I’ve looked into the implementation and found out that this information (the missing transitive dependencies) is indeed already stored in the DepsCppCmake data structure that is responsible for generating the toolchain file. Hence, this should be an easy fix. I’ve reported this issue to the Conan developers and fortunately they seem to agree and will provide a solution probably with the next upcoming version 1.13.

I’m available for software consultancy, training and mentoring. Please contact me, if you are interested in my services.

Image reference: The Barbarians, CC BY-NC-SA 2.0 (https://www.flickr.com/photos/turatti/31861600644/)