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.
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:
Feature | find_package() (System Libs) | FetchContent() (Fetched Source) |
---|---|---|
Build Speed | Fast. Uses pre-compiled binaries. | Slow. Must compile dependency from source on every clean build. |
Reproducibility | Low. Depends on whatever version the user has installed. | High. Fetches a specific Git tag or commit. |
User Setup | High 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) andFetchContent()
(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 tovcpkg.cmake
during the initial configure step. This hijacksfind_package()
and directs it to the libraries managed by vcpkg.
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