Skip to content
C++
Package Manager
Updated 2025-01-01T00:00:00.000Z

Conan 2 — C++ Package Manager

"Conan 2 reference: conanfile.py, conanfile.txt, profiles, generators, binary compatibility, and CMake integration."

TL;DR

Conan 2 is a decentralized C++ package manager with binary caching and full binary compatibility management. Use conanfile.txt for simple projects, conanfile.py for complex ones. Conan Center Index has 1500+ packages.

bash
pip install conan
conan profile detect   # auto-detect your compiler/OS

Quick start with conanfile.txt

ini
# conanfile.txt
[requires]
fmt/10.1.1
nlohmann_json/3.11.3
boost/1.83.0

[generators]
CMakeDeps
CMakeToolchain
bash
# Install dependencies — downloads or builds binaries
conan install . --output-folder=build --build=missing

# Configure CMake using the generated toolchain
cmake -B build -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake
cmake --build build
cmake
# CMakeLists.txt
find_package(fmt REQUIRED)
find_package(nlohmann_json REQUIRED)
target_link_libraries(myapp PRIVATE fmt::fmt nlohmann_json::nlohmann_json)

conanfile.py — for complex requirements

python
# conanfile.py
from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout

class MyAppConan(ConanFile):
    name = "my-app"
    version = "1.0"
    settings = "os", "compiler", "build_type", "arch"
    generators = "CMakeDeps", "CMakeToolchain"

    def requirements(self):
        self.requires("fmt/10.1.1")
        self.requires("openssl/3.1.2")
        if self.settings.os == "Linux":
            self.requires("libpthread-stubs/0.4")

    def build_requirements(self):
        self.tool_requires("cmake/3.27.1")
        self.tool_requires("ninja/1.11.1")

    def layout(self):
        cmake_layout(self)

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

Profiles — compiler configuration

Conan uses profiles to define the compiler, stdlib, and build settings:

ini
# ~/.conan2/profiles/default  (created by: conan profile detect)
[settings]
arch=x86_64
build_type=Release
compiler=gcc
compiler.cppstd=20
compiler.libcxx=libstdc++11
compiler.version=13
os=Linux

Multiple profiles

ini
# ~/.conan2/profiles/clang17
[settings]
arch=x86_64
build_type=Debug
compiler=clang
compiler.cppstd=20
compiler.libcxx=libc++
compiler.version=17
os=Linux

[buildenv]
CC=clang-17
CXX=clang++-17
bash
# Use a specific profile
conan install . --profile=clang17 --output-folder=build --build=missing

# Cross-compile: host profile + build profile
conan install . \
  --profile:host=arm-linux \
  --profile:build=default \
  --output-folder=build --build=missing

Binary management

Conan's key feature: binaries are identified by a hash of (package name, version, settings, options, dependencies). If a matching binary exists in the cache or a remote, it's downloaded. Otherwise, it's built from source.

bash
# Check if binary exists without building
conan install . --output-folder=build

# Force build from source
conan install . --output-folder=build --build=missing  # build only if no binary
conan install . --output-folder=build --build=*        # build everything

# Show binary compatibility hash
conan search fmt/10.1.1 -r conancenter

# List cached packages
conan list "fmt:*"

Package options

Many packages expose options (static/shared, with/without features):

ini
# conanfile.txt
[requires]
boost/1.83.0

[options]
boost*:shared=False       # static linking
boost*:without_python=True
openssl*:shared=True
python
# conanfile.py
def requirements(self):
    self.requires("boost/1.83.0")

def configure(self):
    self.options["boost"].shared = False
    self.options["boost"].without_filesystem = False

Conan Center Index

1500+ pre-built open source packages at conan.io/center:

bash
# Search packages
conan search fmt -r conancenter

# Get package info
conan inspect fmt/10.1.1 -r conancenter

# Common packages
fmt/10.1.1              # {fmt} formatting library
nlohmann_json/3.11.3    # JSON for Modern C++
boost/1.83.0            # Boost libraries
openssl/3.1.2           # TLS/crypto
zlib/1.3                # Compression
sqlite3/3.43.2          # Embedded database
protobuf/4.24.4         # Protocol Buffers
grpc/1.56.2             # gRPC
catch2/3.4.0            # Unit testing
gtest/1.14.0            # Google Test
benchmark/1.8.3         # Google Benchmark
spdlog/1.12.0           # Fast logging
eigen/3.4.0             # Linear algebra

Private Artifactory remote

For enterprise or proprietary packages:

bash
# Add a remote
conan remote add mycompany https://artifactory.mycompany.com/artifactory/api/conan/conan-local

# Login
conan remote login mycompany myuser --password mypassword

# Upload a built package
conan upload "mylib/1.0" -r mycompany

# Install from multiple remotes (searched in order)
conan install . -r conancenter -r mycompany --output-folder=build

CI with GitHub Actions

yaml
- name: Install Conan
  run: pip install conan

- name: Detect profile
  run: conan profile detect

- name: Cache Conan packages
  uses: actions/cache@v4
  with:
    path: ~/.conan2/p
    key: conan-${{ hashFiles('conanfile.*') }}-${{ runner.os }}

- name: Install dependencies
  run: conan install . --output-folder=build --build=missing

- name: Configure and build
  run: |
    cmake -B build -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake
    cmake --build build

Conan 1 → Conan 2 migration

Conan 2 (released 2023) has breaking changes from Conan 1:

Conan 1Conan 2
GeneratorcmakeCMakeDeps + CMakeToolchain
Profile detectionmanualconan profile detect
conanfile.py importsfrom conans import ConanFilefrom conan import ConanFile
self.copy()availableremoved — use copy() from conan.tools.files
cpp_info.libsdirectstill works
build_requiresmethod namerenamed to build_requirements

Writing a Conan Package (conanfile.py as a recipe)

A Conan recipe is just a Python class — source() fetches the code, build() compiles it, package() installs headers and libs, and package_info() tells consumers what to link. Once you understand this lifecycle, packaging any library is mechanical.

python
# conanfile.py (library recipe)
from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout
from conan.tools.files import copy, get
import os

class MyLibConan(ConanFile):
    name = "mylib"
    version = "1.0"
    license = "MIT"
    url = "https://github.com/myorg/mylib"
    description = "A fast C++ utility library"
    settings = "os", "compiler", "build_type", "arch"
    options = {"shared": [True, False], "fPIC": [True, False]}
    default_options = {"shared": False, "fPIC": True}

    def source(self):
        # Fetch from GitHub (tag or commit)
        get(self, "https://github.com/myorg/mylib/archive/refs/tags/v1.0.tar.gz",
            strip_root=True)

    def layout(self):
        cmake_layout(self)

    def generate(self):
        tc = CMakeToolchain(self)
        tc.variables["BUILD_SHARED_LIBS"] = self.options.shared
        tc.generate()

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

    def package(self):
        # In Conan 2: use copy() from conan.tools.files, NOT self.copy()
        copy(self, "*.h",
             src=os.path.join(self.source_folder, "include"),
             dst=os.path.join(self.package_folder, "include"))
        copy(self, "*.a",
             src=self.build_folder,
             dst=os.path.join(self.package_folder, "lib"),
             keep_path=False)
        copy(self, "*.so*",
             src=self.build_folder,
             dst=os.path.join(self.package_folder, "lib"),
             keep_path=False)

    def package_info(self):
        # Tells consumers: target_link_libraries(myapp mylib::mylib)
        self.cpp_info.libs = ["mylib"]
        self.cpp_info.includedirs = ["include"]

Every recipe needs a test_package/ directory so Conan can verify the build before uploading. Keep the test small — just #include the library and call one function.

python
# test_package/conanfile.py
from conan import ConanFile
from conan.tools.cmake import CMake, cmake_layout

class TestMyLib(ConanFile):
    settings = "os", "compiler", "build_type", "arch"
    generators = "CMakeDeps", "CMakeToolchain"

    def requirements(self):
        self.requires(self.tested_reference_str)

    def layout(self):
        cmake_layout(self)

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

    def test(self):
        self.run(os.path.join(self.cpp.build.bindir, "test_mylib"))
bash
# Build and test the package locally
conan create . --version=1.0

# Build with a specific profile
conan create . --version=1.0 --profile=clang17 --build=missing

Conan Lockfiles

Lockfiles pin every resolved version and binary hash so that conan install on a CI machine two months from now uses exactly the same dependency graph as your dev machine today. Check conan.lock into source control the same way you would package-lock.json.

bash
# Generate a lockfile from the current resolution
conan lock create .

# Install using the locked graph — no resolution, fully reproducible
conan install . --lockfile=conan.lock --output-folder=build --build=missing

# Update a single package while keeping the rest pinned
conan lock update conan.lock --requires="fmt/11.0.0"

# Inspect the locked graph
conan graph info . --lockfile=conan.lock

Partial lockfiles are useful when you only want to pin some packages. Use --lockfile-partial to generate a lockfile that only records the packages you specify and leaves others floating.

bash
# Create a partial lockfile (pins fmt and openssl, leaves others free)
conan lock create . --lockfile-partial \
  --requires="fmt/10.1.1" --requires="openssl/3.1.2" \
  --lockfile-out=partial.lock

# Install using the partial lockfile
conan install . --lockfile=partial.lock --output-folder=build --build=missing

Conan Center Index Contribution

Conan Center Index (CCI) is the official, community-maintained recipe repository at github.com/conan-io/conan-center-index. Every package in conancenter comes from a PR there. Contributing your library means anyone with Conan can install it with a single line in conanfile.txt.

The review process works like this: you fork the repo, add your recipe under recipes/<library-name>/, open a PR, and CCI's bot runs hook checks, linting, and test builds across Linux/macOS/Windows. Reviewers ask for changes before merging.

Key checklist for a successful CCI PR:

  • Recipe lives at recipes/<libname>/all/conanfile.py (or versioned sub-dirs for breaking changes)
  • test_package/ is mandatory and must build a real executable
  • source() must download from an official release URL (no Git clones)
  • No vendored dependencies — each dep must be a separate Conan requirement
  • Options follow CCI conventions: shared, fPIC, header_only
  • conan-hooks CI passes: conan-center-index-hooks lint checks naming, license, patches
  • Patch files go in recipes/<libname>/all/patches/ and must be upstreamed or minimal
  • The recipe must build on all three platforms without skipping
bash
# Test your recipe locally against CCI hooks before opening a PR
pip install conan-center-index-hooks
conan create recipes/mylib/all --version=1.0 -pr:b=default -pr:h=default

Cross-Compilation with Conan

Conan separates the machine that builds the code (build profile) from the machine that runs it (host profile) — pass both on the command line and Conan handles the rest, including selecting the right binary packages for each context.

Define a host profile for your target architecture. This example targets a 64-bit ARM Linux board using a GCC 12 cross-compiler installed on an x86_64 Ubuntu host.

ini
# ~/.conan2/profiles/armlinux
[settings]
arch=armv8
build_type=Release
compiler=gcc
compiler.cppstd=20
compiler.libcxx=libstdc++11
compiler.version=12
os=Linux

[buildenv]
CC=aarch64-linux-gnu-gcc-12
CXX=aarch64-linux-gnu-g++-12
AR=aarch64-linux-gnu-ar
STRIP=aarch64-linux-gnu-strip

[conf]
tools.gnu:cross_building=True
tools.build:sysroot=/sysroot/aarch64
bash
# Install deps for the ARM target; build tools stay native
conan install . \
  --profile:host=armlinux \
  --profile:build=default \
  --output-folder=build \
  --build=missing

# CMake picks up the cross-toolchain automatically
cmake -B build \
  -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \
  -DCMAKE_BUILD_TYPE=Release
cmake --build build

tool_requires (e.g., cmake, ninja) are installed for the build profile (your host machine). Regular requires are installed for the host profile (the ARM target). Conan keeps the two graphs completely separate.


Using Conan in Meson Projects

Conan and Meson integrate through the MesonToolchain generator, which writes a native-file (or cross-file for cross-compilation) that Meson reads to find include paths, library paths, and compiler flags provided by Conan packages.

Use the MesonToolchain generator instead of CMakeDeps/CMakeToolchain in your recipe or conanfile.txt:

ini
# conanfile.txt
[requires]
fmt/10.1.1
nlohmann_json/3.11.3

[generators]
MesonToolchain
PkgConfigDeps
bash
# Generate the Meson native file
conan install . -g MesonToolchain --output-folder=build --build=missing

# Pass the native file to Meson setup
meson setup build --native-file build/conan_meson_native.ini

# Build as normal
ninja -C build

For cross-compilation, Conan generates a conan_meson_cross.ini file instead. Pass it with --cross-file to meson setup.

bash
# Cross-compile with Meson + Conan
conan install . \
  --profile:host=armlinux \
  --profile:build=default \
  -g MesonToolchain \
  --output-folder=build \
  --build=missing

meson setup build --cross-file build/conan_meson_cross.ini
ninja -C build

In a conanfile.py-based project, use MesonToolchain inside generate() instead of CMakeToolchain, and replace CMake(self) with Meson(self) in build():

python
from conan import ConanFile
from conan.tools.meson import MesonToolchain, Meson
from conan.tools.layout import basic_layout

class MyAppConan(ConanFile):
    settings = "os", "compiler", "build_type", "arch"
    generators = "PkgConfigDeps"

    def layout(self):
        basic_layout(self, src_folder="src")

    def generate(self):
        tc = MesonToolchain(self)
        tc.generate()

    def build(self):
        meson = Meson(self)
        meson.configure()
        meson.build()

See Also

  • vcpkg — Microsoft's alternative package manager with a curated port tree and CMake integration
  • CPM — lightweight CMake-native dependency manager based on FetchContent
  • CMake — the build system Conan integrates with most tightly
  • Cross-Compilation — deeper coverage of cross-toolchain setup independent of the package manager
Edit on GitHubUpdated 2025-01-01T00:00:00.000Z