02 — Conan 2.x and vcpkg

Both are good in 2026. Both are alive. Neither has “won”. Conan 2.x (major rewrite released January 2023, mature by 2025) is the choice when you need enterprise-grade cross-compilation, private registries, and per-configuration binary caching. vcpkg (Microsoft, since 2016, manifest mode since 2020) is the choice when you want the smallest possible on-ramp and a single Git repo that just builds on cmake --preset default. You are going to learn both, because in studies and in real jobs you will see both, and “I only know one” is a bad answer.

This file is short and prescriptive: enough to get you a working fmt dependency both ways, and enough judgment to pick the right one for a future project. Both examples target CMake integration — you never pass their generated flags to g++ by hand.

1. Conan 2.x — concept model

Conan is written in Python and installs via pip install conan. It has:

  • A recipe for each package (Python file conanfile.py) that knows how to build it from source or fetch a prebuilt binary.

  • A profile describing your build environment (compiler, libcxx, build_type, arch).

  • A remote (default: conancenter, mirrors ConanCenter) that stores recipes and prebuilt binaries keyed by settings + options + dependencies.

  • A cache at ~/.conan2/ holding downloaded recipes and per-configuration binary artifacts.

The consumer file in your project is conanfile.txt (simple) or conanfile.py (programmable). For 90% of projects, start with conanfile.txt.

conanfile.txt

[requires]
fmt/10.2.1
spdlog/1.14.1
googletest/1.15.2
benchmark/1.9.0

[generators]
CMakeDeps
CMakeToolchain

[layout]
cmake_layout

First-time setup

pip install --user conan
conan profile detect --force        # writes a default profile at ~/.conan2/profiles/default

Edit ~/.conan2/profiles/default and confirm it says compiler.cppstd=20 (add it if not):

[settings]
arch=armv8
build_type=Release
compiler=apple-clang
compiler.cppstd=20
compiler.libcxx=libc++
compiler.version=15
os=Macos

Install + build

conan install . --output-folder=build --build=missing
cmake --preset conan-release      # Conan emits this preset automatically
cmake --build --preset conan-release

--build=missing says “if a matching binary isn’t in the remote for my exact settings, build from source”. Set -s build_type=Debug (or profiles) to get a Debug variant cached separately.

CMake side

Conan’s CMakeToolchain generator writes a conan_toolchain.cmake and CMake presets that point at it. Your CMakeLists.txt just does:

find_package(fmt CONFIG REQUIRED)
target_link_libraries(mynn PRIVATE fmt::fmt)

No Conan-specific lines in CMakeLists.txt. That’s the modern separation: Conan populates CMAKE_PREFIX_PATH, CMake handles the rest.

When Conan shines

  • Multiple simultaneous build configurations in one cache (Debug + Release + ASan + cross-compiled for aarch64) each with their own binary artifact.

  • Private artifactory (JFrog Artifactory, Conan Server) for proprietary packages — first-class in Conan.

  • Cross-compilation (host and build profiles) is more explicit and reliable.

  • Custom recipes (conanfile.py) are just Python; expressive, testable.

2. vcpkg (manifest mode) — concept model

vcpkg is a C++ tool (Microsoft, MIT license) bootstrapped from a Git repo. It has:

  • A port for each package (a portfile.cmake + vcpkg.json) checked into the microsoft/vcpkg Git repo.

  • Manifest mode: your project has a vcpkg.json at its root listing dependencies. vcpkg installs them into a per-project vcpkg_installed/ directory. This is the mode you want; classic mode (global installs) is legacy.

  • Binary caching: on by default from GitHub Actions cache, NuGet, or a local dir. Same source → same binary re-used.

  • Baseline pinning: "builtin-baseline" is a Git SHA of the vcpkg repo. Same SHA on every machine = same package versions. Reproducible.

First-time setup (once per machine)

git clone https://github.com/microsoft/vcpkg.git ~/vcpkg
~/vcpkg/bootstrap-vcpkg.sh
export VCPKG_ROOT=~/vcpkg

vcpkg.json at project root

{
  "name": "mynn",
  "version-string": "0.1.0",
  "dependencies": [
    "fmt",
    "spdlog",
    { "name": "gtest", "version>=": "1.15.2" },
    "benchmark"
  ],
  "builtin-baseline": "<full-git-sha-of-vcpkg-repo>"
}

Get the current baseline via cd ~/vcpkg && git rev-parse HEAD and paste it in. Refresh it deliberately when you want new package versions.

Configure with the vcpkg toolchain

cmake -B build -S . \
    -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake \
    -DCMAKE_BUILD_TYPE=Release
cmake --build build

Or in CMakePresets.json:

{
  "name": "vcpkg",
  "inherits": "base",
  "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
  "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" }
}

CMake side

find_package(fmt CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
target_link_libraries(mynn PRIVATE fmt::fmt spdlog::spdlog)

Identical to Conan on the CMake side. Both package managers deliberately hand off to CMake’s find_package(... CONFIG) route.

When vcpkg shines

  • Onboarding: single toolchain flag, and cmake handles the rest. New teammates go from git clone to green build in one step.

  • Ports maintained by Microsoft + community: broadest coverage of Windows-native and cross-platform libraries.

  • Binary caching in GHA is trivial: actions/cache on vcpkg_installed + baseline SHA and you’re done.

  • Baseline as Git SHA = reproducibility by construction.

3. Head-to-head

Dimension

Conan 2.x

vcpkg (manifest)

Package count (2026)

~2000 in ConanCenter

~2500 in vcpkg registry

Written in

Python

C++

Install path

pip install conan

git clone + bootstrap

Reproducibility mechanism

conan.lock + profile

builtin-baseline (Git SHA)

CMake integration

CMakeDeps + CMakeToolchain generators

Single CMAKE_TOOLCHAIN_FILE

Multiple configs cached

First-class, per-config binaries

Yes, with binary caching

Private registries

First-class (Artifactory)

Registry overlays; less polished

Cross-compilation

Explicit host/build profiles

Triplets (arm64-linux, x64-osx)

Ecosystem lean

Enterprise / regulated

Microsoft / cross-platform hobbyist

Learning curve

Steeper (profiles, settings, options)

Gentler (one JSON, one toolchain flag)

4. Which one should you pick?

Honest 2026 guidance:

  • Personal projects, portfolio, ML/data code: vcpkg. Faster onboarding, JSON manifest, CI matrix trivially reproducible. Your ONNX Runtime / Arrow / Eigen dependencies are all there.

  • Enterprise C++ shops with monorepos, cross-compilation, or private packages: Conan 2. Zoho, if it standardizes on C++ package management on the ML platform side, is more likely to reach for Conan for the private-registry + profiles story.

  • Rule for Phase 4: your reference template ships both flavors on different branches (main = vcpkg, conan = Conan). You want both on your resume; the delta between them is one file (vcpkg.jsonconanfile.txt) plus toolchain wiring.

5. Traps and gotchas

Conan

  • Forgetting compiler.cppstd=20 in your profile → your C++20 code silently compiles as C++17. Check with conan profile show.

  • Not pinning --build=missing behavior → sudden multi-hour builds when a new setting doesn’t match the cache.

  • libcxx mismatch on macOS (libc++ vs libstdc++) causing linker errors — profile-level fix.

vcpkg

  • Forgetting to update builtin-baseline → you stay on stale package versions across the whole team.

  • Vendored vcpkg_installed/ in the repo (don’t). It goes in .gitignore.

  • On CI, forgetting to cache vcpkg_installed/ → 20-minute cold builds every run. Use actions/cache keyed on your baseline SHA + vcpkg.json hash.

  • VCPKG_ROOT unset in CI — preset silently falls back or errors. Set it explicitly.

Both

  • Don’t check package-manager output directories into Git.

  • Don’t shadow package-manager-provided targets with your own add_library(fmt ...). Namespaces (fmt::fmt) protect you — use them.

6. What most people get wrong

They pick a package manager, get one dep working, and never look at the second. Then, six months later, at a new job that uses the other one, they can’t tell the story of why a find_package(fmt CONFIG REQUIRED) call resolves. Do both. It’s a weekend. The concepts — recipes/ports, per-config caches, toolchain integration — are the same. Only the syntax differs.

Also wrong: reaching for FetchContent for every dep because it “just works”. Fine for gtest and benchmark (tiny, header-heavy). Terrible for Arrow, Eigen, LibTorch — you’ll rebuild multi-gigabyte deps from scratch on every clean checkout. Use a real package manager for real deps.


Nav: ← 01 Modern CMake · Next: 03 Testing and benchmarks →