C++ Package Management: vcpkg & Conan — Escaping External
Introduction: “How do I add this library?”
Path hell just to include a header
If you already covered CMake and package managers overview, part 40 automates the full project lifecycle. The first step is external libraries. Manually downloading, building, and aligning include paths diverges environments across teammates and tangles versions. vcpkg and Conan are tools where you declare packages and versions, and they fetch, build, and wire paths for your build. vcpkg is Microsoft-led and integrates well with CMake; Conan excels at cross-platform and multi-compiler workflows. Both underpin reproducible builds (same source and settings → same result everywhere). Other ecosystems solve the same problem with Python pip·uv·Poetry·npm·Go modules·Rust Cargo; the build generator is often CMake. See build system comparison for the big picture, and vcpkg·Conan for deep dives.
Analogy
Build·test·deploy pipelines resemble a factory QC line: same inputs should yield same outputs; Sanitizers and static analysis act like pre-shipment defect checks.
Practical note: This article is based on real issues and fixes from large C++ projects. It includes production pitfalls and debugging tips rarely covered in books or docs alone.
Problem scenario: dependency hell
What you actually see
"Teammate A installed under /usr/local, I used /opt."
"It built on Windows but fails on Linux."
"I built with spdlog 1.10, but the server has 1.8."
"find_package(Boost) fails. Where is Boost?"
"Dependency A wants C++17, dependency B wants C++14 — conflict."
Root causes
- Path mismatch: different install paths per developer
- Version mismatch: dev / CI / prod library versions differ
- Platform differences: Windows / Linux / macOS build settings differ
- Dependency trees: A needs B, B needs C — hard to align manually
More scenarios
Scenario 5: new hire onboarding
"I want to build too — where do I get Boost and OpenSSL?"
"I installed a different version — why does only my machine error?"
With manual installs, onboarding can take hours. With vcpkg/Conan plus a one-line README, cmake -B build -S . can be enough.
Scenario 6: CI-only failures
"It works locally but fails only on GitHub Actions."
"After clearing the cache the build broke."
If you depend on libraries installed only locally, CI fails. Manifest mode or a conanfile pins dependencies in the repo so CI matches.
Scenario 7: complex dependency trees
Project → A → B → C
→ D → C
If A and D both need C but different versions, you get “which C?”. vcpkg and Conan perform dependency resolution for you.
Direction
Declarative dependency management: list required packages and versions in vcpkg.json or conanfile.txt, and the tool downloads, builds, and configures paths. Version-controlled with code → reproducible builds.
flowchart LR
subgraph before[Manual management]
B1[Dev A] --> B2[Path A]
B3[Dev B] --> B4[Path B]
B5[CI] --> B6[Path C]
B2 -.->|mismatch| B4
B4 -.->|mismatch| B6
end
subgraph after[Package manager]
A1[vcpkg.json] --> A2[Same dependencies]
A2 --> A3[Dev A]
A2 --> A4[Dev B]
A2 --> A5[CI]
end
Caveat: triplet/profile differences (OS·architecture) remain — document the CI triplet.
Using vcpkg
Installation
# Linux/macOS: clone vcpkg then bootstrap
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
# Windows (PowerShell)
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
.\bootstrap-vcpkg.bat
After install: the vcpkg binary is ./vcpkg (Unix) or vcpkg.exe (Windows). Add to PATH or invoke by absolute path.
Manifest mode (recommended)
In Manifest mode, put vcpkg.json at the project root. When you configure CMake, vcpkg builds and installs declared packages. The “packages this project needs” list is version-controlled → better reproducibility.
Example layout
my-app/
├── CMakeLists.txt
├── vcpkg.json # dependency manifest
└── src/
└── main.cpp
vcpkg.json
{
"name": "my-app",
"version": "1.0.0",
"description": "vcpkg Manifest mode example",
"dependencies": [
"fmt",
"spdlog",
{
"name": "openssl",
"version>=": "3.0.0"
}
],
"builtin-baseline": "a1b2c3d4e5f6"
}
Notes:
dependencies: required packages; bare strings use latest compatible versionsversion>=: minimum versionbuiltin-baseline: commit hash of the vcpkg ports tree — recommended for reproducible builds
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(my-app VERSION 1.0.0 LANGUAGES CXX)
# vcpkg toolchain: use vcpkg packages when configuring CMake
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake"
CACHE STRING "Vcpkg toolchain file")
add_executable(my-app src/main.cpp)
find_package(fmt CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
target_link_libraries(my-app PRIVATE
fmt::fmt
spdlog::spdlog
)
# C++17
target_compile_features(my-app PRIVATE cxx_std_17)
Key point: pointing CMAKE_TOOLCHAIN_FILE at vcpkg’s vcpkg.cmake makes find_package use vcpkg-installed paths automatically.
main.cpp (runnable example)
#include <spdlog/spdlog.h>
#include <fmt/core.h>
int main() {
spdlog::info("Built with vcpkg Manifest mode");
spdlog::info("fmt example: {}", fmt::format("Hello, {}!", "vcpkg"));
return 0;
}
Build steps
# 1. If vcpkg is a git submodule in the project
git submodule add https://github.com/Microsoft/vcpkg.git vcpkg
# 2. CMake configure (vcpkg installs dependencies automatically)
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE="$(pwd)/vcpkg/scripts/buildsystems/vcpkg.cmake"
# 3. Build
cmake --build build
./build/my-app
Expected output: lines like [info] Built with vcpkg Manifest mode.
Triplet
A triplet selects platform·architecture·linking flavor.
| Triplet | Description |
|---|---|
x64-windows | Windows 64-bit |
x64-windows-static | Windows 64-bit static linking |
x64-linux | Linux 64-bit |
arm64-osx | macOS Apple Silicon |
x64-osx | macOS Intel |
# Use a specific triplet
cmake -B build -S . \
-DCMAKE_TOOLCHAIN_FILE="$(pwd)/vcpkg/scripts/buildsystems/vcpkg.cmake" \
-DVCPKG_TARGET_TRIPLET=x64-windows-static
Running multiple triplets in CI enables multi-platform builds.
Classic mode (legacy)
Classic mode uses vcpkg install for a global install. It is weaker for reproducibility than Manifest mode — prefer Manifest for new projects.
vcpkg install fmt spdlog
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake
vcpkg overlays (internal ports)
Use an overlay when a library is missing from the official registry or you maintain a patched port in-house.
# Overlay path
cmake -B build -S . \
-DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake \
-DVCPKG_OVERLAY_PORTS=/path/to/my-ports
Overlay layout:
my-ports/
└── my-internal-lib/
├── portfile.cmake
└── vcpkg.json
Version constraints in vcpkg.json
You can pin ranges precisely in vcpkg.json.
{
"dependencies": [
"fmt",
{
"name": "spdlog",
"version>=": "1.11.0",
"version<": "2.0.0"
},
{
"name": "openssl",
"version>=": "3.0.0",
"platform": "!windows"
}
]
}
version>=: minimum versionversion<: exclusive upper boundplatform: add the dependency only on selected platforms (!windows= not on Windows)
Using Conan
Installation
# Conan 2.x (pip recommended)
pip install conan
# Check version
conan --version
conanfile.txt (simple projects)
conanfile.txt lists package names and versions in a minimal format.
Layout
my-conan-app/
├── CMakeLists.txt
├── conanfile.txt
└── src/
└── main.cpp
conanfile.txt
[requires]
fmt/10.1.1
spdlog/1.12.1
[generators]
CMakeDeps
CMakeToolchain
Notes:
[requires]:name/version, e.g.fmt/10.1.1[generators]:CMakeDeps(forfind_package),CMakeToolchain(toolchain file)
Install and build
# 1. Install Conan deps (generates CMake files under build/)
conan install . --output-folder=build --build=missing
# 2. CMake configure (use Conan toolchain)
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake
# 3. Build
cmake --build build
./build/my-conan-app
--build=missing: build from source when a package is missing in the local cache.
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(my-conan-app VERSION 1.0.0 LANGUAGES CXX)
add_executable(my-conan-app src/main.cpp)
find_package(fmt REQUIRED)
find_package(spdlog REQUIRED)
target_link_libraries(my-conan-app PRIVATE
fmt::fmt
spdlog::spdlog
)
target_compile_features(my-conan-app PRIVATE cxx_std_17)
Important: after conan install, pass CMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake so find_package resolves Conan packages.
main.cpp
#include <spdlog/spdlog.h>
#include <fmt/core.h>
int main() {
spdlog::info("Built with Conan");
spdlog::info("fmt: {}", fmt::format("Hello, Conan!"));
return 0;
}
conanfile.py (advanced)
Use conanfile.py for finer control over build scripts, options, and requirements.
from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMakeDeps, cmake_layout
from conan.tools.build import check_min_cppstd
class MyAppConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
generators = "CMakeDeps", "CMakeToolchain"
def layout(self):
cmake_layout(self)
def requirements(self):
self.requires("fmt/10.1.1")
self.requires("spdlog/1.12.1")
def configure(self):
check_min_cppstd(self, "17")
def generate(self):
deps = CMakeDeps(self)
deps.generate()
tc = CMakeToolchain(self)
tc.generate()
Profiles
A profile defines compiler, build type (Release/Debug), and architecture.
# Show default profile
conan profile show default
# Profile paths
# Linux: ~/.conan2/profiles/default
# Windows: C:\Users\<user>\.conan2\profiles\default
Example profile (~/.conan2/profiles/default):
[settings]
os=Linux
os_build=Linux
arch=x86_64
arch_build=x86_64
compiler=gcc
compiler.version=12
compiler.libcxx=libstdc++11
build_type=Release
Share profiles across team and CI for aligned environments.
# Use a specific profile
conan install . --output-folder=build --build=missing -pr=./conanprofile
Conan options
Some packages expose options to change build behavior.
# conanfile.txt
[requires]
spdlog/1.12.1
[options]
spdlog:shared=False
spdlog:wchar_support=True
In conanfile.py, use default_options or set options in configure.
def configure(self):
self.options["spdlog"].shared = False
Searching recipes
# Search Conan Center
conan search spdlog --remote=conancenter
# List available versions
conan search spdlog --remote=conancenter -q="*"
Conan build flow
sequenceDiagram
participant Dev as Developer
participant Conan as Conan
participant Cache as Cache
participant CMake as CMake
Dev->>Conan: install
Conan->>Cache: package?
alt exists
Cache-->>Conan: provide
else missing
Conan->>Conan: build
Conan->>Cache: store
end
Conan->>Dev: toolchain
Dev->>CMake: configure
CMake->>Conan: find_package
Conan-->>CMake: pass paths
vcpkg vs Conan comparison
| Topic | vcpkg | Conan |
|---|---|---|
| Integration | One CMake toolchain line | conan install + CMakeToolchain |
| Package count | Official registry 2000+ | Conan Center 1000+ |
| Custom builds | Port patches·overlays | Full control via conanfile.py |
| Multi-compiler | Via triplet | Via profile |
| Internal packages | Overlay ports | Private repos (Artifactory, etc.) |
| Cache | Local buildtrees | Conan cache (~/.conan2) |
Selection guide
- vcpkg: Windows·Visual Studio·CMake-first, Microsoft ecosystem
- Conan: exotic compilers, distributing internal libraries as packages, stronger cross-platform needs If you already standardized on vcpkg, keep going; if internal libraries ship as Conan packages, Conan may fit better. Pick one convention for the team and CI and stick to it.
Common errors and fixes
vcpkg
Error 1: “Could not find a package configuration file”
CMake Error: Could not find a package configuration file provided by "fmt"
Cause: CMAKE_TOOLCHAIN_FILE not set, or vcpkg has not built that package yet.
Fix:
# 1. Verify toolchain file
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake
# 2. If vcpkg.json exists but packages are missing, reconfigure from scratch
# (Manifest mode installs during CMake configure)
rm -rf build
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=...
Error 2: “A suitable version of cmake was not found”
Error: vcpkg was unable to find the version of cmake in your PATH
Cause: vcpkg uses CMake internally; missing from PATH or too old. Fix:
# CMake 3.20+ recommended
which cmake
cmake --version
# Add to PATH or point vcpkg at the cmake binary
Error 3: “Port xxx is not in the baseline”
Error: Could not find a version that satisfies the requirement ...
Cause: builtin-baseline is stale or the package is not in that baseline.
Fix:
# Refresh baseline
cd vcpkg
git pull
git rev-parse HEAD # put this hash in vcpkg.json builtin-baseline
{
"builtin-baseline": "latest_commit_hash"
}
Conan
Error 4: “fmt/10.1.1: NotFound”
ERROR: Package 'fmt/10.1.1' not found
Cause: version not on Conan Center or remote misconfiguration. Fix:
# List available versions
conan search fmt --remote=conancenter
# Check remotes
conan remote list
# conancenter should be present
Error 5: “find_package(fmt) failed”
Cause: CMAKE_TOOLCHAIN_FILE not set to Conan’s conan_toolchain.cmake.
Fix:
# Run conan install first
conan install . --output-folder=build --build=missing
# Pass toolchain to CMake (adjust path)
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=$(pwd)/build/conan_toolchain.cmake
Error 6: “Binary not found” / “Can’t find a compatible binary”
ERROR: Missing prebuilt package for 'fmt/10.1.1'
Cause: no prebuilt binary for your compiler/arch/settings. Fix:
# Build from source
conan install . --output-folder=build --build=missing
# Build only one package
conan install . --output-folder=build --build=fmt
Shared
Error 7: “C++ standard mismatch”
error: #error "spdlog requires C++17 or later"
Cause: project builds as C++14 but a dependency needs C++17. Fix:
# In CMakeLists.txt
target_compile_features(myapp PRIVATE cxx_std_17)
# or
set(CMAKE_CXX_STANDARD 17)
Error 8: “Multiple definitions” / link errors
Cause: wrong link of header-only libs, or static/dynamic mix. Fix:
- vcpkg: unify triplet, e.g.
x64-windows-static - Conan: same
build_typein profile for everyone
Error 9: vcpkg “Building package … failed”
Building package xxx:x64-linux failed
Cause: compile error while building port, missing system deps, etc. Fix:
# Verbose logs
export VCPKG_VERBOSE=1
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=...
# Try building one package manually
./vcpkg install xxx --debug
Error 10: Conan “Version conflict”
ERROR: Conflict in spdlog/1.12.1: requirement 'fmt/9.x' conflicts with 'fmt/10.1.1'
Cause: transitive version clash — spdlog wants fmt 9.x but you require 10.1.1. Fix:
# Inspect the graph
conan graph info . --format=html
# Adjust versions in conanfile — e.g. align fmt with spdlog
[requires]
fmt/9.1.0
spdlog/1.12.1
Error 11: “conan_toolchain.cmake not found”
Cause: conan install not run, or --output-folder does not match CMake -B.
Fix:
# Match output-folder and CMake build dir
conan install . --output-folder=build --build=missing
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake
# build/ must be the same directory
Best practices
vcpkg
- Use Manifest mode:
vcpkg.jsonat repo root, committed to Git - Pin
builtin-baseline: use a specific ports commit for reproducibility - Specify versions: prefer
"version>="and similar when possible - Submodule vcpkg:
git submodule add https://github.com/Microsoft/vcpkg.git vcpkg - Cache in CI: cache vcpkg buildtrees to shorten CI time
Conan
conanfile.txtorconanfile.py: choose by team complexity- Share profiles: commit a
conanprofile(or equivalent) to the repo - Use lockfiles:
conan lock createto pin the graph - Cache
~/.conan2in CI
Shared
- Minimize dependencies: add only what you need
- Pin versions in production where practical
- Document in README: vcpkg or Conan and exact build steps
Details
vcpkg: baseline update cadence
builtin-baseline points at a specific vcpkg ports commit. Updating periodically brings security and bug fixes, but to avoid surprise breakage, test on a branch before bumping baseline.
# Update baseline to latest vcpkg commit
cd vcpkg && git pull
git rev-parse HEAD # write this into vcpkg.json
Conan: lockfiles
# Create conan.lock (pinned graph)
conan lock create .
# CI uses the same graph
conan install . --output-folder=build --lockfile=conan.lock --build=missing
Committing conan.lock keeps everyone on the same dependency versions.
find_package and CONFIG mode
Most vcpkg-installed packages ship Config packages. Prefer find_package(fmt CONFIG REQUIRED) over legacy Module mode when available.
# Recommended: CONFIG mode
find_package(fmt CONFIG REQUIRED)
target_link_libraries(myapp PRIVATE fmt::fmt)
# Module mode (older packages)
find_package(Boost REQUIRED COMPONENTS system)
Production patterns
Pattern 1: vcpkg Manifest in CI
# .github/workflows/build.yml (example)
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive # includes vcpkg submodule
- name: Configure CMake
run: |
cmake -B build -S . \
-DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake \
-DCMAKE_BUILD_TYPE=Release
- name: Build
run: cmake --build build --config Release
Pattern 2: Conan lockfile for reproducibility
conan lock create .
conan install . --output-folder=build --lockfile=conan.lock --build=missing
Pattern 3: Multi-platform builds (vcpkg)
strategy:
matrix:
triplet: [x64-linux, x64-windows, arm64-osx]
steps:
- run: |
cmake -B build -S . \
-DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake \
-DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }}
Pattern 4: Private Conan remote
conan remote add company https://artifactory.company.com/artifactory/api/conan/conan-local
# In conanfile.txt
[requires]
company-internal-lib/1.0.0
fmt/10.1.1
Pattern 5: Docker with vcpkg
# Dockerfile example (vcpkg)
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y git cmake g++ build-essential
RUN git clone https://github.com/Microsoft/vcpkg.git /vcpkg && \
/vcpkg/bootstrap-vcpkg.sh
ENV CMAKE_TOOLCHAIN_FILE=/vcpkg/scripts/buildsystems/vcpkg.cmake
WORKDIR /app
COPY . .
RUN cmake -B build -S . && cmake --build build
Pattern 6: Conan + Docker
# Dockerfile example (Conan)
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3-pip cmake g++ build-essential
RUN pip install conan
WORKDIR /app
COPY . .
RUN conan install . --output-folder=build --build=missing
RUN cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake && \
cmake --build build
Pattern 7: CI cache for Conan and vcpkg
# vcpkg cache (GitHub Actions)
- name: Cache vcpkg
uses: actions/cache@v4
with:
path: ${{ env.VCPKG_ROOT }}/buildtrees
key: vcpkg-${{ runner.os }}-${{ hashFiles('**/vcpkg.json') }}
# Conan cache
- name: Cache Conan
uses: actions/cache@v4
with:
path: ~/.conan2
key: conan-${{ runner.os }}-${{ hashFiles('**/conanfile.txt', '**/conanfile.py') }}
Pattern 8: vcpkg + CMake Presets
CMakePresets.json shares one configure setup across the team.
{
"version": 3,
"configurePresets": [
{
"name": "vcpkg-default",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake"
}
}
]
}
cmake --preset vcpkg-default
cmake --build build
Pattern 9: Dependency update strategy
1. Dev: periodically bump vcpkg baseline or Conan versions
2. Staging: full test pass after bumps
3. Production: only validated versions in lockfile/baseline
Summary
- vcpkg: Manifest mode + CMake toolchain → declarative deps and reproducible builds; strong on Windows·CMake.
- Conan:
conanfile+ profiles for compiler and build type, CMake generators for integration; strong for cross-platform and internal packages.
Implementation checklist
- Choose vcpkg or Conan per team convention
- Declare deps with Manifest (
vcpkg) orconanfile(Conan) - Set
CMAKE_TOOLCHAIN_FILEcorrectly - Pin C++ standard (e.g. C++17)
- Include build steps in CI
- Document build steps in README
FAQ
Q. vcpkg or Conan?
A. Favor vcpkg for Windows·Visual Studio·Microsoft-centric workflows; consider Conan for unusual compilers, internal package distribution, or stronger cross-platform needs. If the team already picked one, standardize on it.
Q. Can I mix system packages and a package manager?
A. You can, but link and ABI conflicts are common when the same library comes from two places (for example a distro package and a vcpkg/Conan build). Prefer a single source of dependencies within one project. Declare dependencies with vcpkg or Conan and automate build and link setup. Next, read CI/CD·GitHub Actions (#40-2). Previous: [High-performance C++ #39-3] SIMD and parallelization: acceleration with std::execution and intrinsics