Using Conan with CMake
Discover Conan, the binary-first C++ package manager, and learn how to integrate it with CMake using profiles and lockfiles for fast, reproducible builds.
In our last couple of chapters, we've focused on fetching and managing external dependencies. We started with the manual find_package()
approach that required libraries to already be installed. We then introduced the reproducible but potentially slow FetchContent
approach. Then, we introduced vcpkg
as a package manager that streamlines the process.
While FetchContent
and vcpkg
give you ultimate control and transparency by building from source. However, this has a cost: it increases the amount of time required to generate a clean build. Compiling a large library from scratch can take over ten minutes, and a realistic project can have multiple such dependencies.
This lesson introduces Conan, the other major player in the C++ package management world. Conan takes a different philosophical approach - one that prioritizes speed through managing precompiled binaries. We'll learn how Conan works, how it differs from vcpkg, and how to integrate it into our projects.
Conan vs. vcpkg
To understand Conan, it's best to contrast it with what we already know about vcpkg.
As we saw, vcpkg's default behavior is to download the source code of a dependency and compile it locally. This is simple, transparent, and guarantees that the library is built with the exact same compiler and settings as your main project.
To tell CMake that it needs to do this, we provide a toolchain file as part of the configure step. This toolchain file - vcpkg.cmake
- is a fixed, standard configuration provided by the vcpkg team for use in all projects.

Conan has a different approach. The first part of the workflow doesn't use CMake at all - we instead use the conan install
command line tool to read our manifest and download the requested libraries.
To help with this, we provide a profile, which explains our build requirements - target operating system, architecture, build type, etc. This tells Conan which exact binaries it needs to download.

When it completes its work, we have a load of new files in our build directory. The most important are the libaries we requested, as well as a conan_toolchain.cmake
file that it generated for us.
From that point on, we just use CMake as normal, using this toolchain file to set up the integration

The major advantage of Conan's binary-first approach is speed. For a large library, downloading a pre-compiled binary can turn a 30-minute compilation into a 30-second download. For teams working on large projects, this can save thousands of developer-hours.
To do this, it manages repositories of pre-compiled binary packages. When you request a dependency, Conan checks the configuration in the profile we provide and tries to download a matching binary from a server (called a "remote")
The default remote is conancenter
, a public repository of almost 2,000 open-source libraries at the time of writing. We can also provide additional remotes as needed.
If Conan can't find a binary matching our target requirements on a remote, it can instead download the source code and trigger a compilation. The profile we provide can include information about the compilers that Conan can use if it needs to.
Installation and Basic Configuration
Let's get Conan installed. Unlike vcpkg, Conan is distributed as a Python package, so the first step is to ensure you have Python and its package manager, pip
, installed.
Installation on Windows (Visual Studio)
Install Python: The easiest way is from the Microsoft Store or the official Python website. During installation, make sure the option "Add Python to PATH" is checked.
Install Conan: Open a new Command Prompt or PowerShell terminal (to ensure the PATH changes are active) and run:
pip install conan
Installation on MSYS2 UCRT64
Install Python and pip: From the UCRT64 terminal, use pacman
:
pacman -S mingw-w64-ucrt-x86_64-python-pip
Install Conan: We can now use pip
to install conan
:
pip install conan
Installation on macOS
The simplest method is to use Homebrew, which will manage the Python dependency for you.
brew install conan
Installation on Linux (Debian/Ubuntu)
Install pip:
sudo apt update
sudo apt install python3-pip
Install Conan:
pip install conan
Working with Profiles
Conan configuration is mostly managed through profiles. We can create an initial, default profile using the following command:
conan profile detect --force
This command probes your system to detect your OS, compiler, and other settings, and then creates a default profile based on what it finds.
We can list the profiles on our system using the command:
conan profile list
For now, we should only have one - the default
profile we just created:
Profiles found in the cache:
default
We can get the location of where a profile is saved by using the conan profile path
command. For example, to find where the default profile is stored, we'd use the command:
conan profile path default
C:/Users/gregf/.conan2/profiles/default
In some cases, we may want or need to change the default profile that Conan generated. We do that simply by opening the profile in a text editor, making our changes, and saving it.
Multiple Profiles
Conan is designed on the basis that we typically need multiple profiles, carrying different configuration values. For example, we'll cover cross-compilation in the next chapter, which requires us to use a different profile from what was generated for our system.
To create a new profile, we run the conan profile detect
command again, passing a --name
for our new profile.
For example, we might create a new profile for compiling using gcc
in Debug
mode like this:
conan profile detect --name gcc-debug
Saving detected profile to C:/Users/gregf/.conan2/profiles/gcc-debug
If your setup matches our introductory chapter where we installed GCC on Windows, a working profile might look something like this:
[settings]
os=Windows
arch=x86_64
compiler=gcc
compiler.version=15
compiler.cppstd=20
compiler.libcxx=libstdc++11
build_type=Debug
[buildenv]
CC=gcc
CXX=g++
In our future commands, we can specify what profile we want Conan to use via a --profile
argument. For example, we'll soon introduce the conan install
command. The following version of that command will use our default profile:
conan install .
But, if we wanted to use a different profile, we'd add a --profile
or --pr
argument, passing our profile name:
conan install . --pr gcc-debug
Using Conan
Let's integrate Conan into our Greeter
project to manage the spdlog
dependency. The process involves four steps: creating the Conan manifest, installing the dependencies, updating our CMakeLists.txt
, and finally configuring our project.
Step 1: Create the Conan Manifest
In the root directory of the Greeter
project, create a file named conanfile.txt
. This file serves the same purpose as vcpkg.json
.
conanfile.txt
[requires]
spdlog/1.15.3
[generators]
CMakeDeps
CMakeToolchain
The [requires]
section lists all the direct dependencies your project needs. We're asking for version 1.15.3
of spdlog
.
The [generators]
section tells Conan which integration files to create for CMake.
CMakeDeps
: This generator creates the necessaryfind_package
configuration files (spdlog-config.cmake
, etc.) for each dependency.CMakeToolchain
: This generator creates a singleconan_toolchain.cmake
file that configures CMake with all the settings from our active Conan profile (compiler, build type, etc.).
Step 2: Install the Dependencies
Now, let's install our dependencies. We first ensure we're back in our /build
directory. We may also want to clean out everything in that directory, to ensure we're configuring from scratch.
Remember, we can navigate our terminal to a directory using the cd
command:
cd /path/to/project-root/build
From that location, we run conan install
. This command reads a conanfile.txt
, fetches the packages (ideally as pre-compiled binaries), and generates the CMake files in a specified output folder.
We need to provide three arguments:
- The location of our
conanfile.txt
. If we're in the/build
directory, and ourconanfile.txt
is in the project root, we'd pass..
to refer to the parent directory. --output-folder
specifies where to place generated files. We can put them in the current directory,/build
, by passing.
--build=missing
tells Conan to build from source if a binary is not found
Our command is as follows:
conan install .. --output-folder=. --build=missing
If we wanted to use a non-default profile, we'd additionally pass its name as a --profile
or --pr
argument here.
After this command completes, our /build
directory will contain a load of new files. The most important one is the conan_toolchain.cmake
file, which we'll use soon.
Step 3: Update CMakeLists.txt
Similar to vcpkg, Conan's integration doesn't require any changes or additions to our CMakeLists.txt
files. They don't need to know anything about Conan; they just call find_package()
call and the conan_toolchain.cmake
file (which we'll use in the next step) ensures that the package can be found:
app/CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
add_executable(GreeterApp src/main.cpp)
# This call is intercepted by the Conan toolchain
find_package(spdlog CONFIG REQUIRED)
target_link_libraries(GreeterApp
PRIVATE
GreeterLib
spdlog::spdlog
)
A simple test app that uses our Conan-provided dependency might look something like this:
app/src/main.cpp
#include <greeter/Greeter.h>
#include <spdlog/spdlog.h>
int main() {
spdlog::info(get_greeting());
return 0;
}
Step 4: Configure the CMake Project
Finally, we configure our project from the build
directory. We should set the CMAKE_TOOLCHAIN_FILE
to the toolchain file that Conan just generated for us, and we should provide the CMAKE_BUILD_TYPE
. The build type should match the build_type
value in the Conan profile we're using:
cmake .. \
-DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake \
-DCMAKE_BUILD_TYPE="Debug"
This command tells CMake to use Conan's configuration. The conan_toolchain.cmake
file will, in turn, teach find_package()
how to locate the libraries that Conan installed.
If everything has been configured correctly, we can build and run our project as usual.
cmake --build .
./app/GreeterApp
[2025-08-20 16:18:41.740] [info] Hello from the modular greeter library!
Ensuring Reproducible Builds with Lockfiles
We've solved the speed issue, but what about reproducibility? Just like with vcpkg, we need a way to ensure that every developer on the team gets the exact same version of every dependency, every time.
We saw vcpkg's primary mechanism for this was a baseline. Most other dependency managers, including Conan, solve this using a lockfile. A lockfile is text file stored and shared alongside the project that lists the entire dependency graph, capturing the exact version and revision of every direct and transitive dependency.
In Conan's case, this file is called conan.lock
. The process of creating and using it looks like this:
- Generate the Lockfile: A developer generates the lockfile using a
conan
command. - Share the Lockfile: The
conan.lock
file is shared alongside your project. For example, it would be committed to your version control repository. - Use the Lockfile: Everyone else on the team uses this committed lockfile during their
conan install
step.
Let's walk through these steps. First, we generate the lockfile using the conan lock create
command, passing the location of our conanfile.txt
. If we're in the /build
directory and the conanfile.txt
is in the project root, we'd pass ..
:
conan lock create ..
This creates a conan.lock
file in the same location as the conanfile.txt
. It might look something like this:
conan.lock
{
"version": "0.5",
"requires": [
"spdlog/1.15.3#3ca0e9e6b83af4d0151e26541d140c86%1748617970.519",
"fmt/11.2.0#579bb2cdf4a7607621beea4eb4651e0f%1746298708.362"
],
"build_requires": [],
"python_requires": [],
"config_requires": []
}
You can see this file has recorded the version used for our direct dependency spdlog
, as well as for the indirect fmt
dependency that spdlog
uses internally. It also tracks a revision hash and timestamp of each version, for even more assurance that we're getting the exact package we intended.
You should share this lockfile as part of your project, and then everyone else can use it for installation when doing a conan install
. By default, Conan will find and use a conan.lock
file that is in the same location as our conanfile.txt
, so we just install things as we did before:
conan install .. --output-folder=.
Behind the scenes, Conan will find and respect the conan.lock
file:
Using lockfile: 'D:/Projects/cmake/conan.lock'
...
Install finished successfully
Now, every single build, on every machine, will use an identical set of dependencies. We can explicitly provide a lockfile to the install
command using the --lockfile
argument:
conan install .. \
--lockfile="../some-other-lockfile.lock" \
--output-folder=.
To prevent a lockfile being used, even if one is found next to our conanfile.txt
, we'd pass an empty string:
conan install .. --lockfile="" --output-folder=.
Updating Lockfiles
When we add dependencies to our project, or update the version of existing dependencies, our conan.lock
file will fall out of date. For example, let's imagine we want to introduce the tabulate
library:
conanfile.txt
[requires]
spdlog/1.15.3
tabulate/1.4
[generators]
CMakeDeps
CMakeToolchain
If we try a conan install
now, we'll get an error:
conan install .. --output-folder=.
Using lockfile: 'D:/Projects/cmake/conan.lock'
...
ERROR: Requirement 'tabulate/1.4' not in lockfile 'requires'
Beyond creating new lockfiles, the conan lock
command has several other utilities that are helpful for managing and updating lockfiles. You can read more about them on the official documentation.
However, one of the more common way of updating lockfiles is through passing arguments like --lock-partial
and --lockfile-out
arguments as part of a conan install
command. The following command both installs our dependencies, and updates our conan.lock
file:
conan install .. \
--output-folder=. \
--lockfile-partial \
--lockfile-out="../conan.lock"
The --lockfile-partial
argument instructs Conan to use the lockfile versions for libraries that are referenced in the lockfile - spdlog
and fmt
, in our case. However, for anything not in the lockfile - tabulate
, in our case - just use the normal resolution process, as if the lockfile didn't exist.
The --lockfile-out
argument instructs Conan to write or update the lockfile, using the versions of everything it just installed. We should now see our new tabulate/1.4
requirement tracked in our lockfile:
conan.lock
{
"version": "0.5",
"requires": [
"tabulate/1.4#c6ab7bb7413f268432d2684a1a27bbc1%1681259966.699",
"spdlog/1.15.3#3ca0e9e6b83af4d0151e26541d140c86%1748617970.519",
"fmt/11.2.0#579bb2cdf4a7607621beea4eb4651e0f%1746298708.362"
],
"build_requires": [],
"python_requires": [],
"config_requires": []
}
Cleaning Lockfiles
If we want to upgrade one of our dependencies to a new version, we can update our conanfile.txt
in the usual way:
[requires]
spdlog/1.15.3
tabulate/1.4
tabulate/1.5
[generators]
CMakeDeps
CMakeToolchain
If we rerun our previous conan install
command with the same arguments, our new dependency (tabulate/1.5) will be tracked, but tabulate/1.4 will still be lurking in our lockfile:
conan install .. \
--output-folder=. \
--lockfile-partial \
--lockfile-out="../conan.lock"
conan.lock
{
"version": "0.5",
"requires": [
"tabulate/1.5#7e35e527405fa23bdb97323cc0e61e24%1681259966.66",
"tabulate/1.4#c6ab7bb7413f268432d2684a1a27bbc1%1681259966.699",
"spdlog/1.15.3#3ca0e9e6b83af4d0151e26541d140c86%1748617970.519",
"fmt/11.2.0#579bb2cdf4a7607621beea4eb4651e0f%1746298708.362"
],
"build_requires": [],
"python_requires": [],
"config_requires": []
}
This is mostly harmless, but we can add the --lockfile-clean
argument to our conan install
command to also remove any unused dependencies from our lockfile:
conan install .. \
--output-folder=. \
--lockfile-partial \
--lockfile-out="../conan.lock" \
--lockfile-clean
conan.lock
{
"version": "0.5",
"requires": [
"tabulate/1.5#7e35e527405fa23bdb97323cc0e61e24%1681259966.66",
"spdlog/1.15.3#3ca0e9e6b83af4d0151e26541d140c86%1748617970.519",
"fmt/11.2.0#579bb2cdf4a7607621beea4eb4651e0f%1746298708.362"
],
"build_requires": [],
"python_requires": [],
"config_requires": []
}
We can clean a lockfile independently of an installation using the conan lock create
command, passing the location of our conan.lock
file and the --lockfile-clean
argument:
conan lock create .. --lockfile-clean
Summary
Conan offers a fast and flexible alternative for dependency management. While it requires a bit more initial setup than vcpkg, its focus on pre-compiled binaries can dramatically accelerate compilation times for complex projects.
- Binary-First Philosophy: Conan's main advantage is speed. It avoids compiling dependencies from source by downloading pre-compiled binaries that match your configuration.
- Profiles Define Environments: Conan uses profile files to understand your build environment (OS, compiler, etc.), which is the key to finding the correct binaries.
- Clean CMake Integration: The
CMakeDeps
andCMakeToolchain
generators allow for a clean integration where yourCMakeLists.txt
only needs a standardfind_package()
call. - Reproducibility Through Lockfiles: Generating and sharing a
conan.lock
file is the standard way to help with reproducible builds.
Cross-Compilation with Conan
Learn to cross-compile C++ projects with Conan, using build and host profiles to manage dependencies for different platforms.