Conan
Conan is a decentralized, open-source package manager designed specifically for C++‘s complex Binary compatibility requirements. Unlike vcpkg, which defaults to a source-based model, Conan Operates on a Binary-First model.
Compiling dependencies like Qt, Boost, or LLVM from source for every CI run or developer machine is Inefficient. Conan resolves this by caching pre-compiled binaries for specific configurations (Operating System, Compiler, Architecture, Build Type). If a matching binary exists on the remote Server, Conan downloads it; otherwise, it builds from source and caches the result.
The Binary Compatibility Graph
Section titled “The Binary Compatibility Graph”The core architectural innovation of Conan is the Package ID.
When a project requests a dependency (e.g., fmt/10.1.0), Conan computes a SHA-1 hash (the Package ID) based on the input configuration:
- Settings: Global project configuration (OS, Arch, Compiler Version, C++ Standard).
- Options: Package-specific configuration (Shared/Static,
fPIC``with_ssl). - Requirements: Version of transitive dependencies.
Resolution Logic
Section titled “Resolution Logic”- Compute Hash: The client calculates the Package ID required for the current environment.
- Query Remote: The client asks the remote registry (ConanCenter or Artifactory) if a binary artifact with that ID exists.
- Retrieval:
- Hit: Download the binary (headers and libs).
- Miss: Download the recipe (
conanfile.py), build from source locally, and (optionally) upload the new binary to the remote.
Package ID Calculation: Formal Treatment
Section titled “Package ID Calculation: Formal Treatment”The Package ID is a SHA-1 hash computed over the concatenation of:
\mathrm{PackageID = \mathrm{SHA1(\mathrm{settings \| \mathrm{options \| \mathrm{requires)Where settings is a sorted dictionary of os``arch``compiler``compiler.version compiler.libcxx``build_typeEtc. The options dictionary is package-specific. The requires List captures transitive dependency versions.
Theorem: Two machines with identical profiles and identical dependency version constraints will Compute the same Package ID.
Proof: SHA-1 is a deterministic function: identical inputs always produce identical outputs. The Profile defines the settings dictionary. The conanfile.txt (or conanfile.py) defines the options and requires. If both are identical on two machines, the SHA-1 input is identical, and Therefore the output is identical.
Corollary: If two machines have the same Package ID for a dependency, they can share the same Pre-compiled binary. This is the foundation of Conan’s binary caching.
Profile Configuration
Section titled “Profile Configuration”A Conan Profile is a text file that defines the “Settings” used to calculate the Package ID. It Represents the target platform’s state.
Conan 2.0 mandates a separation between two contexts to support cross-compilation:
- Build Profile: The machine running the build (e.g., x86_64 Linux CI Agent).
- Host Profile: The machine that will run the artifacts (e.g., ARM64 Embedded Device).
Anatomy of a Profile
Section titled “Anatomy of a Profile”Profiles are stored in ~/.conan2/profiles/. A standard profile named linux-clang-release might Look like this:
[settings]os=Linuxarch=x86_64compiler=clangcompiler.version=16compiler.libcxx=libstdc++11compiler.cppstd=23build_type=Release
[options]# Force all dependencies to be static by default*:shared=False# Specific overrideopenssl/*:shared=True
[tool_requires]# Inject build tools into the environmentcmake/3.27.0ninja/1.11.1Profile Composition
Section titled “Profile Composition”Profiles supports inheritance using the include() directive, allowing architectural layering.
base_linux:
[settings]os=Linuxcompiler=gcccompiler.version=13compiler.libcxx=libstdc++11debug_x64:
include(base_linux)[settings]arch=x86_64build_type=DebugCross-Compilation Profiles
Section titled “Cross-Compilation Profiles”For cross-compilation, Conan requires separate build and host profiles:
# profile/build-linux (the machine running the compiler)[settings]os=Linuxarch=x86_64compiler=gcccompiler.version=13compiler.libcxx=libstdc++11build_type=Release# profile/host-arm64 (the target device)[settings]os=Linuxarch=armv8compiler=gcccompiler.version=13compiler.libcxx=libstdc++11build_type=Releasecompiler.cppstd=20conan install . --build=missing \ -pr:b=profile/build-linux \ -pr:h=profile/host-arm64Dependency Specification
Section titled “Dependency Specification”Dependencies are declared in a conanfile.txt (declarative) or conanfile.py (imperative) at the Project root.
The Declarative Approach (conanfile.txt)
Section titled “The Declarative Approach (conanfile.txt)”For consumption-only workflows, conanfile.txt is sufficient.
[requires]fmt/10.1.1nlohmann_json/3.11.2spdlog/1.12.0
[generators]# Generates CMake toolchain files and target definitionsCMakeDepsCMakeToolchain
[options]spdlog/*:header_only=TrueThe Imperative Approach (conanfile.py)
Section titled “The Imperative Approach (conanfile.py)”For complex logic (e.g., conditional dependencies based on OS), use Python.
from conan import ConanFile
class SystemRecipe(ConanFile): settings = "os", "compiler", "build_type", "arch" generators = "CMakeDeps", "CMakeToolchain"
def requirements(self): self.requires("fmt/10.1.1") if self.settings.os == "Windows": self.requires("pthreads4w/3.0.0")
def layout(self): # Standardizes build folder structures (cmake_layout) from conan.tools.cmake import cmake_layout cmake_layout(self)CMake Integration
Section titled “CMake Integration”Conan 2.0 integration relies on generating a CMake Toolchain File that injects the dependency Paths into the build system. This process decouples the package manager from the build system.
Workflow
Section titled “Workflow”Install Dependencies: Run Conan to resolve the graph and generate toolchains.
Terminal window conan install . --output-folder=build --build=missing -pr:h=linux-clang-release -pr:b=default
-pr:h: Host profile (Target).-pr:b: Build profile (Build Agent).--build=missing: Build from source if binary is not found.
Configure CMake: Pass the generated toolchain to CMake.
Terminal window cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=ReleaseBuild:
Terminal window cmake --build build
CMakeLists.txt Configuration
Section titled “CMakeLists.txt Configuration”Inside CMakeLists.txtTreat Conan packages as standard targets. The CMakeDeps generator creates Config files for find_package.
cmake_minimum_required(VERSION 3.25)project(App LANGUAGES CXX)
find_package(fmt REQUIRED)find_package(nlohmann_json REQUIRED)
add_executable(App main.cpp)target_link_libraries(App PRIVATE fmt::fmt nlohmann_json::nlohmann_json)Remote Architecture and Artifactory
Section titled “Remote Architecture and Artifactory”In a professional environment, reliance on the public ConanCenter is often restricted due to Security or availability concerns. Organizations deploy JFrog Artifactory or Sonatype Nexus As private Conan remotes.
Remote Configuration
Section titled “Remote Configuration”# Add a private remoteconan remote add internal-artifacts https://artifactory.company.com/artifactory/api/conan/conan-local
# Authenticateconan user -p <password> -r internal-artifacts <username>The CI/CD Pipeline Flow
Section titled “The CI/CD Pipeline Flow”- Build Job:
- Checkout Source.
conan create .(Builds the package locally).conan upload <package> -r internal-artifacts.
- Consumer Job:
conan install . -r internal-artifacts.- Conan detects the pre-built binary in Artifactory matching the CI profile.
- Downloads binary (seconds) instead of compiling (minutes).
Creating Packages (conan create)
Section titled “Creating Packages (conan create)”The conan create command is the primary mechanism for building and publishing Conan packages. It Executes a five-stage pipeline defined in your conanfile.py:
The Five Stages of conan create
Section titled “The Five Stages of conan create”| Stage | Method in conanfile.py | Purpose |
|---|---|---|
layout | layout() | Configure source/build/package folder structure |
source | source() | Download/extract source code from remote |
build | build() | Compile the library (invoke CMake, Make, etc.) |
package | package() | Copy build artifacts (headers, libs) into package folder |
test | test() | Run consumer tests against the built package |
Complete Recipe Example
Section titled “Complete Recipe Example”The following is a production-ready conanfile.py for a library named mylib that depends on fmt:
from conan import ConanFilefrom conan.tools.cmake import CMake, CMakeToolchain, cmake_layoutfrom conan.tools.files import copy, rmdirimport os
class MyLibConan(ConanFile): name = "mylib" version = "1.0.0" license = "MIT" author = "Team" description = "A high-performance data processing library" topics = ("cpp", "data", "processing") settings = "os", "compiler", "build_type", "arch" exports_sources = "src/*", "CMakeLists.txt", "include/*" generators = "CMakeDeps", "CMakeToolchain"
def requirements(self): self.requires("fmt/10.1.1", transitive_headers=True)
def layout(self): cmake_layout(self)
def build(self): cmake = CMake(self) cmake.configure() cmake.build()
def package(self): cmake = CMake(self) cmake.install()
def package_info(self): self.cpp_info.libs = ["mylib"] self.cpp_info.bindirs = ["lib"] self.cpp_info.includedirs = ["include"]
# Platform-specific system libraries if self.settings.os == "Linux": self.cpp_info.system_libs = ["pthread", "dl"] elif self.settings.os == "Windows": self.cpp_info.system_libs = ["ws2_32"]
# Compile definitions to propagate to consumers if self.settings.build_type == "Debug": self.cpp_info.defines = ["MYLIB_DEBUG"]Package ID Calculation
Section titled “Package ID Calculation”When conan create runs, it computes a SHA-1 hash from the combined settings, options, and Transitive dependency versions. Two machines with identical profiles will compute the same Package ID and can share binaries.
# Inspect the computed package IDconan inspect . -a package_id
# View the dependency graph with package IDsconan graph info . --format=dotpackage_info() — The Contract with Consumers
Section titled “package_info() — The Contract with Consumers”The package_info() method is critical: it defines what consumers see when they find_package(mylib). It specifies:
self.cpp_info.libs: Library files to link (without prefix/suffix).self.cpp_info.system_libs: System libraries (e.g.,pthread``dl``ws2_32).self.cpp_info.defines: Preprocessor definitions consumers should inherit.self.cpp_info.cxxflags: Compiler flags to propagate.self.cpp_info.bindirs/includedirs: Search paths for binaries and headers.
def package_info(self): self.cpp_info.libs = ["mylib"] if self.settings.os == "Linux": self.cpp_info.system_libs = ["pthread", "dl"] elif self.settings.os == "Windows": self.cpp_info.system_libs = ["ws2_32"] if self.settings.build_type == "Debug": self.cpp_info.defines.append("MYLIB_DEBUG")CMake Toolchain Integration Details
Section titled “CMake Toolchain Integration Details”What CMakeToolchain Generates
Section titled “What CMakeToolchain Generates”The CMakeToolchain generator creates a conan_toolchain.cmake file that sets CMake variables Before the project’s CMakeLists.txt is processed. This file configures:
| CMake Variable | Purpose |
|---|---|
CMAKE_C_COMPILER / CMAKE_CXX_COMPILER | Compiler executable path (from build profile) |
CMAKE_BUILD_TYPE | Build type (Debug, Release, RelWithDebInfo) |
CMAKE_POSITION_INDEPENDENT_CODE | Set to ON when building shared libraries |
BUILD_SHARED_LIBS | Controlled by the *:shared option |
CMAKE_PREFIX_PATH | Paths to dependency package folders |
What CMakeDeps Generates
Section titled “What CMakeDeps Generates”CMakeDeps creates FindXXX.cmake or XXXConfig.cmake files in the build directory, enabling find_package():
build/ Generators/ fmt/ fmtConfig.cmake <- find_package(fmt) uses this fmtTargets.cmake <- defines fmt::fmt target nlohmann_json/ nlohmann_jsonConfig.cmakeTwo-Phase Workflow (CMake Presets Integration)
Section titled “Two-Phase Workflow (CMake Presets Integration)”Conan 2.x integrates with CMake presets to eliminate manual toolchain passing:
# Step 1: Install dependencies and generate CMakePresets.jsonconan install . --output-folder=build --build=missing
# Step 2: CMake automatically picks up the presetcmake --preset conan-releasecmake --build --preset conan-releaseThis is the recommended workflow for Conan 2.x. The conan install command generates a CMakePresets.json in the build folder that configures the toolchain and build directories.
Version Ranges and Conflict Resolution
Section titled “Version Ranges and Conflict Resolution”Version Ranges
Section titled “Version Ranges”Conan supports semantic version ranges in dependency specifications:
[requires]fmt/[>=10.0 <11.0] # Accept any 10.x versionboost/[>=1.82.0] # Accept 1.82.0 and aboveVersion ranges resolve to the latest matching version on the remote. This is useful for getting Patch updates without manually bumping versions.
Conflict Resolution
Section titled “Conflict Resolution”When two dependencies require different versions of the same package, Conan raises a conflict error:
ERROR: Conflict in mylib/1.0.0: Requirement fmt/10.0.0 conflicts with fmt/9.1.0 required by other_lib/2.0.0Resolution strategies:
Narrow the version range to force compatibility:
[requires]fmt/10.1.1 # Pin exact versionUse
conan lockto lock the entire dependency graph:Terminal window conan lock create . --lockfile=conan.lockconan install . --lockfile=conan.lock --lockfile-out=conan.lock
The lock file records exact versions and package IDs for every transitive dependency, ensuring reproducible builds across machines and time.
- Override a specific dependency version:
Terminal window conan install . --build=missing -o 'other_lib/*:fmt_version=10.1.1'
build_requires vs requires
Section titled “build_requires vs requires”Conan 2.x distinguishes between two types of dependencies:
requires (Runtime Dependencies)
Section titled “requires (Runtime Dependencies)”These are libraries that your project links against at runtime. They contribute to the Package ID Hash and their binaries are needed by consumers.
def requirements(self): self.requires("fmt/10.1.1") self.requires("openssl/3.1.0")tool_requires (Build-Time Dependencies)
Section titled “tool_requires (Build-Time Dependencies)”These are tools needed during the build process but not at runtime. They do not affect the Package ID and are not propagated to consumers. In Conan 2.x, build_requires is replaced by tool_requires in the [tool_requires] section of profiles or in the recipe.
from conan.tools.cmake import CMakeDeps, CMakeToolchain
class MyLibConan(ConanFile): tool_requires = "cmake/3.28.1" # or equivalently: # def tool_requires(self): # self.tool_requires("cmake/3.28.1")| Aspect | requires | tool_requires |
|---|---|---|
| Affects Package ID | Yes | No |
| Propagated to consumers | Yes (transitive) | No |
| Examples | fmt``openssl``boost | cmake``ninja``meson |
| Binary needed at runtime | Yes | No |
| Build-time only | No | Yes |
Binary Cache and Local Cache
Section titled “Binary Cache and Local Cache”The Conan 2.x Cache Structure
Section titled “The Conan 2.x Cache Structure”Conan 2.x stores all data in ~/.conan2/:
~/.conan2/ cache/ # Local binary package storage pkg/ # Organized by recipe reference <hash>/ <package_id_hash>/ # Package artifacts profiles/ # Profile files remotes.json # Remote registry configurationEach package is identified by its reference (fmt/10.1.1) and package ID (a SHA-1 hash of Settings/options). The cache deduplicates packages across projects: if two projects depend on fmt/10.1.1 with the same compiler settings, they share the same cached binary.
Cache Management Commands
Section titled “Cache Management Commands”# List all cached packagesconan list "*"
# Search for a specific packageconan search "fmt/*" -r conancenter
# Remove a specific package from cacheconan remove "fmt/10.0.0*"
# Remove all cached binaries (force rebuild from source)conan remove "*" -p -b
# Inspect package contentsconan cache path fmt/10.1.1:<package_id_hash>Conan 1 vs Conan 2: Migration Guide
Section titled “Conan 1 vs Conan 2: Migration Guide”Conan 2.x introduced breaking changes designed to simplify the API and improve performance. The Following table highlights the key differences.
| Feature | Conan 1.x | Conan 2.x |
|---|---|---|
| Configuration file | conan.conf in ~/.conan | No global config (profile-based) |
| Recipe imports | from conans import ConanFile | from conan import ConanFile |
| CMake integration | self.settings.compiler.libcxx | compiler.libcxx in profile |
| Build method | def build(self) with CMake(self) | Same, but CMake from conan.tools.cmake |
| Package info | self.cpp_info | Same (compatible) |
| Generators | cmake``cmake_find_package | CMakeDeps``CMakeToolchain |
| Install command | conan install . -s ... | conan install . -pr:... |
| Remote commands | conan remote add | Same (compatible) |
| Lock files | conan lock create | Same (compatible) |
| Cache location | ~/.conan/data/ | ~/.conan2/cache/ |
| Python API | Many deprecated functions | Cleaned-up, fewer functions |
build_requires | self.build_requires("cmake/...") | self.tool_requires("cmake/...") |
Key Migration Steps
Section titled “Key Migration Steps”- Change
from conans import ConanFiletofrom conan import ConanFile. - Replace
cmakegenerator withCMakeDepsandCMakeToolchain. - Move
compiler.libcxxfrom the recipe to the profile. - Replace
self.build_requireswithself.tool_requires. - Clear the Conan 1.x cache:
rm -rf ~/.conan.
Conan vs vcpkg: Architectural Trade-offs
Section titled “Conan vs vcpkg: Architectural Trade-offs”| Aspect | Conan | vcpkg |
|---|---|---|
| Binary model | Binary-first: downloads pre-built binaries | Source-first: compiles from source by default |
| Package format | Python-based conanfile.py (flexible) | vcpkg.json + portfile.cmake (CMake-centric) |
| C++ Standard | Separate build/host profiles for cross-compilation | Triplets (simpler but less flexible) |
| Registry | Any remote (JFrog, S3, local filesystem) | Built-in GitHub catalog |
| Binary caching | Built-in, per-configuration SHA-1 based | Binary caching via NuGet feeds or Azure |
| Versioning | Semantic version ranges | Versions in port manifest |
| Ecosystem | Widely used in embedded, gaming, enterprise | Microsoft-backed, strong on Windows |
Conan’s binary-first approach makes it significantly faster for CI pipelines where the same Dependency is built repeatedly. Vcpkg’s source-first approach guarantees reproducibility (you always Build from source on your exact toolchain) but at the cost of build time.
Common Pitfalls
Section titled “Common Pitfalls”- Not using
conan lockfor CI. Without a lock file, CI builds can silently pick up new dependency versions that pass on one configuration but break on another. Always generate a lock file and commit it to version control. - Mixing build and host profiles in native builds. On native (non-cross-compiled) builds, both the build and host profiles should match. If they differ, Conan may download a binary built for the wrong platform, causing cryptic runtime crashes.
- Forgetting
--build=missing. If a binary does not exist on the remote and you do not specify--build=missingConan fails with a “binary not found” error. Use--build=missingfor local development and--build=neverfor CI (where all binaries should come from the remote). - Not pinning the CMake version in
tool_requires. Different CMake versions generate different project files. If your CI uses CMake 3.28 but your local machine uses CMake 3.25, the generated build files may differ. Pin CMake in your profile:cmake/3.28.1in[tool_requires]. - Using
conanfile.txtfor complex projects. The declarative format does not support conditional dependencies, custom build steps, ortest()methods. Once your project needs OS-specific dependencies or custom packaging logic, migrate toconanfile.py. - Ignoring ABI compatibility. Conan’s Package ID does not capture ABI changes caused by compiler flags like
-D_GLIBCXX_USE_CXX11_ABI. If two TUs are compiled with different ABI settings, linking them causes crashes. Ensure your profile captures all ABI-affecting settings. - Stale Conan 1.x cache. If you have both Conan 1.x and 2.x installed, the
conancommand may invoke the wrong version. Verify withconan --versionand useconan2as an alias if necessary. The caches are separate (~/.conanvs~/.conan2).
Conan Recipe: Packaging a Header-Only Library
Section titled “Conan Recipe: Packaging a Header-Only Library”Header-only libraries have a simplified Conan recipe because there is no build step and no binary Artifact to package:
from conan import ConanFilefrom conan.tools.files import copyimport os
class HeaderOnlyLibConan(ConanFile): name = "header_only_lib" version = "2.0.0" license = "BSL-1.0" description = "A header-only signal/slot library" settings = "os", "compiler", "build_type", "arch" exports_sources = "include/*"
def package_id(self): # Header-only: all configurations produce the same package self.info.clear()
def package(self): copy(self, "*.hpp", dst="include", src=os.path.join(self.source_folder, "include")) copy(self, "*.h", dst="include", src=os.path.join(self.source_folder, "include"))
def package_info(self): self.cpp_info.bindirs = [] self.cpp_info.libdirs = []The key technique is self.info.clear() in package_id()Which tells Conan that all compiler Settings produce the same package (since there is no binary). This means only one package is ever Cached, regardless of the consumer’s profile.
Conan Recipe: Packaging a Library with Options
Section titled “Conan Recipe: Packaging a Library with Options”For libraries with configurable features (e.g., enable/disable SSL, choose threading model):
from conan import ConanFilefrom conan.tools.cmake import CMake, CMakeToolchain, cmake_layoutfrom conan.tools.files import copyimport os
class FeatureLibConan(ConanFile): name = "feature_lib" version = "3.1.0" license = "MIT" settings = "os", "compiler", "build_type", "arch"
options = { "shared": [True, False], "fPIC": [True, False], "with_ssl": [True, False], "threading": ["none", "std", "boost"], }
default_options = { "shared": False, "fPIC": True, "with_ssl": False, "threading": "std", }
exports_sources = "src/*", "CMakeLists.txt", "include/*"
def config_options(self): if self.settings.os == "Windows": self.options.rm_safe("fPIC")
def configure(self): if self.options.shared: self.options.rm_safe("fPIC")
def requirements(self): if self.options.with_ssl: self.requires("openssl/3.1.0")
def layout(self): cmake_layout(self)
def generate(self): tc = CMakeToolchain(self) tc.variables["WITH_SSL"] = self.options.with_ssl tc.variables["THREADING"] = self.options.threading tc.generate()
def build(self): cmake = CMake(self) cmake.configure() cmake.build()
def package(self): cmake = CMake(self) cmake.install()
def package_info(self): self.cpp_info.libs = ["feature_lib"] if self.options.with_ssl: self.cpp_info.defines.append("FEATURE_LIB_SSL")The options dictionary defines package-specific configuration that affects the Package ID. Each Unique combination of option values produces a different binary, preventing ABI mismatches.
Conan Center and Private Repositories
Section titled “Conan Center and Private Repositories”ConanCenter
Section titled “ConanCenter”ConanCenter is the public repository of Conan packages maintained by the community. It contains Thousands of popular C++ libraries with tested recipes.
# Search for a packageconan search "fmt/*" -r conancenter
# Get package informationconan inspect fmt/10.1.1 -r conancenterPrivate Repository Setup
Section titled “Private Repository Setup”Organizations deploy JFrog Artifactory as their private Conan remote:
# Configure Artifactory remoteconan remote add company https://artifactory.company.com/artifactory/api/conan/company-libs
# Authenticateconan remote login company -p <token> <username>
# Upload a packageconan upload mylib/1.0.0 -r company
# Download and useconan install . -r company --build=missingSee Also
Section titled “See Also”- Dependency Resolution — Package manager taxonomy
- vcpkg — Source-first alternative
- CPM.cmake — Lightweight source-based alternative
- Binary Caching — Conan’s binary caching architecture
Summary
Section titled “Summary”This topic covers the essential concepts and techniques related to conan, including key principles and practical applications.
Key concepts include:
- core concepts and definitions
- key principles and frameworks
- practical applications
- common techniques and methods
- evaluation and critical analysis
A thorough understanding of these concepts, combined with regular practice and review, is essential for mastery of this topic.
Worked Examples
Section titled “Worked Examples”Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.