Integrating CMake and vcpkg

Learn to manage C++ dependencies effortlessly with the vcpkg package manager and its seamless CMake integration using toolchain files and manifests.

Greg Filak
Published

In the last two lessons, we've explored two ways of handling dependencies: finding them on the system with find_package() and fetching them from source with FetchContent(). Both are powerful, but they represent a trade-off: find_package() is fast but relies on external state, while FetchContent() is reproducible but can be slow to configure and build.

What if we could have the best of both worlds? What if a tool could automatically download, build, and install libraries for us, and then make them available to a clean, simple find_package() call?

This is the role of a C++ package manager. For years, the lack of a standard, cross-platform package manager was a major pain point in the C++ ecosystem, especially when compared to languages like Python (pip) or Rust (cargo). Today, that has changed.

This lesson introduces vcpkg, a free, open-source package manager from Microsoft that has become an industry standard. We'll learn how to use its modern, manifest-based workflow to declare dependencies and integrate them seamlessly into our CMake project.

Why Use a Package Manager?

Let's review the benefits and problems with asking consumers to preinstall packages to support find_package(), and with fetching them on every clean build using FetchContent() or similar techniques:

Featurefind_package() (System Libs)FetchContent() (Fetched Source)
Build SpeedFast. Uses pre-compiled binaries.Slow. Must compile dependency from source on every clean build.
ReproducibilityLow. Depends on whatever version the user has installed.High. Fetches a specific Git tag or commit.
User SetupHigh Effort. User must manually install all dependencies correctly.Low Effort. Just needs Git and a compiler.

A package manager like vcpkg improves our workflow and project in several ways:

  • Centralized Declaration: All direct dependencies are listed in a single "manifest" file stored in the project root, making it easy to see what your project needs at a glance.
  • Decoupled from the Build System: Dependencies are managed by the vcpkg tool itself based on this manifest file. This keeps your CMakeLists.txt files cleaner and focused on their core job: defining how to build your code, not how to acquire and build third-party code.
  • Standardized and Discoverable Packages: With FetchContent() you need to find the Git repository URL and a specific tag for every dependency. vcpkg provides a central, curated registry of thousands of libraries. You can easily search for and add packages by a single, standardized name.

Installing vcpkg

Before we can use vcpkg, we need to install it. The process involves cloning its Git repository and running a one-time bootstrap script.

Note that we should not run the following commands in our usual ./build directory. We want to install vcpkg into a permanant location on our system so it can be used across multiple projects.

We're free to choose whatever location we want. As a reminder, we can move our terminal to the installation location we want to use via the cd command.

For example, if we were on Windows and wanted to install vcpkg in c:\tools, we'd first use the command:

cd c:\tools

On macOS and Linux, a typical location might be ~/vcpkg or ~/dev/vcpkg

cd ~

You should make a note of the installation directory you use here, as we'll need it later.

Installation on Linux (Debian/Ubuntu)

First, ensure you have the necessary prerequisites installed using your distribution's package manager. For Debian-based systems like Ubuntu, this would be:

sudo apt update
sudo apt install git-all

Next, clone the vcpkg repository, and then cd into the directory that this creates:

git clone https://github.com/microsoft/vcpkg.git
cd vcpkg

Finally, run the bootstrap script. This will compile the vcpkg tool itself.

./bootstrap-vcpkg.sh

Installation on macOS

On modern macOS releases, you can simply try to use git from the terminal:

git --version

If it's not already installed, this command will guide you through the process. Alternatively, using Homebrew:

brew install git

Next, clone the vcpkg repository to a location of your choice:

git clone https://github.com/microsoft/vcpkg.git
Cloning into 'vcpkg'...
Updating files: 100%, done

Finally, cd into the vcpkg directory this creates and run the bootstrap script to build the vcpkg executable:

cd vcpkg
./bootstrap-vcpkg.sh

Installation on Windows (Visual Studio)

On Windows, we open the Developer Command Prompt for Visual Studio and clone the repository:

git clone https://github.com/microsoft/vcpkg.git

We then cd into the directory that this creates:

cd vcpkg

And finally run the bootstrap-vcpkg.bat file that vcpkg includes to complete the setup:

./bootstrap-vcpkg.bat

Installation for MSYS2 UCRT64

If you are using the MSYS2 UCRT64 terminal, you first need to install the prerequisites using pacman.

pacman -S --needed git mingw-w64-ucrt-x86_64-toolchain

Then, from the UCRT64 terminal, clone and bootstrap vcpkg:

git clone https://github.com/microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh

When using MSYS2, we may also need to set the VCPKG_TARGET_TRIPLET cache variable to help vcpkg understand our environment. We can set this to x64-mingw-static on the command line as part of the configure step on every clean build:

cmake .. -DVCPKG_TARGET_TRIPLET="x64-mingw-static"

Alternatively, we can hardcode it into our CMakeLists.txt file before the project() command:

CMakeLists.txt

cmake_minimum_required(VERSION 3.16)

# This should be BEFORE project()
set(
  VCPKG_TARGET_TRIPLET "x64-mingw-static"
  CACHE STRING "The vcpkg triplet to use"
)

project(Greeter)

add_subdirectory(greeter)
add_subdirectory(app)

Later in the course, we introduce CMake presets, which give us a better way to manage configuration such as this.

Integrating with CMake

Now that vcpkg is installed, we'll go back to running commands from our project's /build directory as before:

cd /path/to/project/build

How does CMake know to use vcpkg? You tell it by setting the CMAKE_TOOLCHAIN_FILE variable during the configure step. We've already seen this variable - it is what we used to "rewire" CMake to make it use a cross-compiler.

In this lesson, we use this same technique to rewire CMake to use vcpkg.

To do this, we set our CMAKE_TOOLCHAIN_FILE variable to point to a /scripts/buildsystems/vcpkg.cmake that vcpkg provides within the directory we installed it to.

We'll walk through a practical example soon, but the configure command we use would look something like this:

cmake .. -DCMAKE_TOOLCHAIN_FILE="~/vcpkg/scripts/buildsystems/vcpkg.cmake"

This single setting is all it takes to activate vcpkg for your project. Most notably, it empowers find_package() commands in our CMakeLists.txt files to let them find dependencies managed by vcpkg:

Remember, this value gets cached in our build folder, so we only need to set it on our first configure. However, if we perform a clean build by deleting the contents of our build folder, we'll need to reapply this variable.

Alternatively, we can set it from our CMakeLists.txt file, removing the need to set it on the command line:

CMakeLists.txt

cmake_minimum_required(VERSION 3.16)

set(
  CMAKE_TOOLCHAIN_FILE "~/vcpkg/scripts/buildsystems/vcpkg.cmake"
  CACHE STRING "The toolchain file"
)

project(Greeter)

add_subdirectory(greeter)
add_subdirectory(app)

Later in the course, we introduce presets, which improves the developer experience of applying commonly-used cache variables.

Declaring Dependencies with vcpkg.json

With vcpkg installed, we can now declare our project's dependencies. The modern way to do this is with a manifest file named vcpkg.json, placed at the root of your project. This file is the single source of truth for all your project's external libraries.

Let's create one for our Greeter project and add spdlog as a dependency. The syntax looks like this:

vcpkg.json

{
  "dependencies": [
    "spdlog"
  ]
}

One of the benefits of package managers is that they maintain a registry of common packages. Previously, we had to tell CMake exactly where spdlog was. We did that by providing a URL to its git repository.

vcpkg already knows about spdlog and, at the time of writing, around 2,600 other packages. To use one of those packages, we just need to reference it by name.

We typically add some additional metadata to our vcpkg.json, such as our project's name, version, and description:

vcpkg.json

{
  "name": "Greeter",
  "version": "1.0.0",
  "license": "MIT",
  "description": "An impressive program that says hello to people",
  "homepage": "https://www.studyplan.dev",
  "dependencies": [
    "spdlog"
  ]
}

Using Packages

Using a dependency that came from vcpkg looks identical to using any other external dependency. We find it using find_package() and link it in the normal way.

We'll update our app/CMakeLists.txt to find and link spdlog:

app/CMakeLists.txt

cmake_minimum_required(VERSION 3.16)

add_executable(GreeterApp src/main.cpp)

find_package(spdlog CONFIG REQUIRED) 

target_link_libraries(GreeterApp
  GreeterLib
  spdlog::spdlog
)

And we'll ensure our app/src/main.cpp is using the library:

app/src/main.cpp

#include <greeter/Greeter.h>
#include <spdlog/spdlog.h> 

int main() {
  spdlog::info(get_greeting()); 
  return 0;
}

Now, let's configure our project from the build directory. We should do this from a clean build directory.

Remember to update the -DCMAKE_TOOLCHAIN_FILE argument in the following example, replacing ~/vcpkg with the location that you installed vcpkg in:

cmake .. -DCMAKE_TOOLCHAIN_FILE="~/vcpkg/scripts/buildsystems/vcpkg.cmake"

When you run this, you'll see vcpkg spring into action. It will read your vcpkg.json, notice that spdlog is required but not yet installed for your platform, and automatically download, build, and install it.

The output will also share what targets have become available to link against. In this case, it's spdlog::spdlog, which we're already using:

Installing spdlog...
Elapsed time to handle spdlog: 21.4 ms
Packages installed in this vcpkg installation declare the following licenses:
MIT
The package spdlog provides CMake targets:

    find_package(spdlog CONFIG REQUIRED)
    target_link_libraries(main PRIVATE spdlog::spdlog)

All requested installations completed successfully in: 71.7 ms

If the configure step completed successfully, we should build and run our program in the usual way to ensure everything works:

cmake --build .
./app/GreeterApp
[2025-08-14 14:03:19.423] [info] Hello from the modular greeter library!

Summary

This lesson introduced vcpkg, a modern C++ package manager that streamlines dependency management by combining the speed of pre-compiled binaries with the reproducibility of declarative manifests.

  • Why a Package Manager?: It solves the trade-off between find_package() (fast but requires manual user setup) and FetchContent() (reproducible but slow to build from source).
  • Installation: We learned how to install vcpkg by cloning its repository and running the one-time bootstrap script.
  • Manifest Mode: Modern vcpkg uses a vcpkg.json file in your project's root to declare all dependencies in one place.
  • CMake Integration: Activating vcpkg is as simple as pointing the CMAKE_TOOLCHAIN_FILE variable to vcpkg.cmake during the initial configure step. This hijacks find_package() and directs it to the libraries managed by vcpkg.
Next Lesson
Lesson 33 of 37

vcpkg Commands and Versioning

Learn the day-to-day commands for managing dependencies with vcpkg and how to guarantee reproducible builds using baselines and version overrides

Have a question about this lesson?
Answers are generated by AI models and may not be accurate