Skip to content

vcpkg, Conan, FetchContent, and pkg-config

There is no universal winner. Choose a workflow based on supported platforms, binary reproducibility, library publication, organizational registries, and how much dependency source should join the main build.

vcpkg uses manifests and triplets

A manifest declares direct dependencies:

json
{
  "name": "report-cli",
  "version-string": "1.0.0",
  "dependencies": ["fmt", "zlib"]
}

Manifest mode resolves ports from a registry baseline. Triplets describe target architecture and linkage policy; custom triplets can carry organization choices. CMake integration commonly uses the vcpkg toolchain file at initial configure time, after which ordinary find_package calls discover imported targets.

Do not set a toolchain file after the build directory was configured. Cache keys, installed packages, and compiler detection are already established.

Registries and version constraints support controlled dependency sources. Binary caching avoids rebuilding identical package configurations across machines and CI.

Conan models settings, options, and generators

Conan recipes describe how packages are built and consumed. Profiles capture compiler, version, architecture, build type, runtime, and options. Conan 2 generators can produce CMake toolchain and dependency files:

sh
conan install . --output-folder=build --build=missing -s build_type=Debug
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake
cmake --build build

Exact layout depends on the recipe and chosen generators. Lockfiles and remotes support reproducibility and private package workflows. Package IDs determine which settings/options produce distinct binaries; getting that model right is essential for ABI-sensitive options.

FetchContent brings source into the configure graph

cmake
include(FetchContent)
FetchContent_Declare(
  fmt
  GIT_REPOSITORY https://github.com/fmtlib/fmt.git
  GIT_TAG e69e5f977d458f2650bb346dadf2ad30c5320281 # fmt 10.2.1
)
FetchContent_MakeAvailable(fmt)
target_link_libraries(report-cli PRIVATE fmt::fmt)

Pin immutable commits, as the example does, and plan offline or mirrored builds. A release tag is easier to read but can move unless repository policy prevents it. Dependency options and policies may enter the parent configure scope. FetchContent does not provide a global binary package model or transitive lockfile by itself.

find_package is discovery, not acquisition

Config mode loads package-provided metadata such as FooConfig.cmake; module mode uses a CMake FindFoo.cmake. Both should yield imported targets when modern integration is available.

Discovery searches prefixes, registries, toolchain-provided roots, and other locations. Debug unexpected resolution by asking CMake for find diagnostics and inspecting the chosen package directory—do not merely add paths until configuration turns green.

pkg-config exposes compiler and linker metadata

Unix-oriented libraries often install .pc files:

sh
pkg-config --cflags --libs libcurl

The output contains flags suitable for the detected installation. CMake's FindPkgConfig can convert this into imported targets. PKG_CONFIG_PATH and sysroot-aware variables affect discovery; cross builds must not accidentally consume host .pc files.

System managers and NuGet have scoped roles

Homebrew, APT, DNF, and similar tools are excellent for system-integrated developer dependencies and tools. They do not guarantee the same version across every target. NuGet works naturally for managed packages and some Windows native assets, but native C++ variants and build integration require package-specific conventions.

Choose one authoritative dependency graph

Avoid committing several lockfiles and letting developers pick arbitrarily. Document:

  • direct dependencies and allowed sources;
  • baseline/lock update process;
  • target profiles or triplets;
  • binary-cache trust and retention;
  • offline behavior;
  • vulnerability and license review;
  • how CMake receives imported targets.

Optional prompts

Explain: Why must a vcpkg or Conan CMake toolchain usually be selected on the first configure?

Answer: It participates in compiler/platform and dependency discovery cached during configuration. Adding it later can leave inconsistent cached results.

Explain: What does find_package not normally promise?

Answer: Acquisition. It discovers compatible package metadata in configured locations; another manager, install step, or source integration must make the package available.

Further reference