If you've ever tried to install a C++ library and felt like you were assembling furniture without instructions, this article is for you. We're going to talk about vcpkg manifest mode and how it works with CMake, and I'm going to explain it like you're five years old (in a good way — no judgment here).
First, Let's Talk About the Problem
In most programming languages, adding a library is easy. Python has pip install requests. JavaScript has npm install express. You type one command, and boom, the library shows up in your project.
C++ never really had that. For decades, if you wanted to use a library like fmt or nlohmann/json, you had to:
- Download the source code yourself
- Figure out how to compile it
- Tell your compiler where to find the headers
- Tell your linker where to find the compiled binaries
- Cry a little
vcpkg is Microsoft's answer to this mess. It's a package manager for C++ — like pip or npm, but for C++ libraries. And manifest mode is the modern, recommended way to use it.
Classic Mode vs. Manifest Mode (The "Dummy" Explanation)
Think of vcpkg like a grocery store.
-
Classic mode is like walking into the store and physically putting items into a shared pantry in your house. Every project on your computer shares that one pantry. If Project A needs an old version of a can of beans and Project B needs a new version, you've got a problem — they're fighting over the same can.
-
Manifest mode is like writing a shopping list (a file called
vcpkg.json) and keeping a separate fridge for each project. Every project gets its own ingredients, at exactly the versions it asked for. Nothing gets mixed up. Nothing conflicts.
Manifest mode is the modern way, and it's what almost everyone should use today. So that's what we're covering.
Step 1: Get vcpkg Onto Your Machine
You only need to do this once, ever (not once per project).
# Clone vcpkg from GitHub
git clone https://github.com/microsoft/vcpkg.git
# Go into the folder
cd vcpkg
# Run the bootstrap script (this builds the vcpkg tool itself)
./bootstrap-vcpkg.sh # macOS/Linux
# or
.\bootstrap-vcpkg.bat # Windows
That's it. You now have a vcpkg executable sitting in that folder. Think of it as installing the "grocery store app" on your phone. You haven't bought anything yet — you've just installed the tool that lets you shop.
Step 2: Create Your Project Folder
Let's make a simple example project.
my-cool-app/
├── CMakeLists.txt
├── vcpkg.json
└── src/
└── main.cpp
Nothing fancy. A CMake file, a manifest file, and some source code.
Step 3: Write the Shopping List — vcpkg.json
This is the heart of manifest mode. This file tells vcpkg exactly what libraries your project needs.
{
"name": "my-cool-app",
"version": "1.0.0",
"dependencies": [
"fmt",
"nlohmann-json"
]
}
Let's break this down like you're new here:
"name"— the name of your project. Lowercase, no spaces."version"— your project's version number."dependencies"— a plain list of library names you want. Here we're grabbingfmt(a popular text-formatting library) andnlohmann-json(a very popular JSON library).
That's genuinely it. You don't specify where to download them from or how to build them. vcpkg already knows, because it maintains a giant catalog of thousands of libraries.
Step 4: Write Your CMakeLists.txt
CMake is the tool that actually compiles your project. Here's a minimal setup that plays nicely with vcpkg.
cmake_minimum_required(VERSION 3.20)
project(my_cool_app LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find the libraries vcpkg installed for us
find_package(fmt CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
add_executable(my_cool_app src/main.cpp)
# Link the libraries to our executable
target_link_libraries(my_cool_app
PRIVATE
fmt::fmt
nlohmann_json::nlohmann_json
)
What's happening here, in plain English:
find_package(fmt CONFIG REQUIRED)— "Hey CMake, go find thefmtlibrary that vcpkg installed, and fail loudly if you can't find it."target_link_libraries(...)— "Attach these libraries to my program so it actually knows how to use them at compile time."
You are not manually specifying include paths or .lib/.so file locations anywhere. vcpkg + CMake handle all of that plumbing behind the scenes. This is the magic of manifest mode.
Step 5: Write Some Actual Code
Just so we have something to compile — src/main.cpp:
#include <fmt/core.h>
#include <nlohmann/json.hpp>
int main() {
// Using fmt to print nicely formatted text
fmt::print("Hello, {}! You are {} years old.\n", "World", 25);
// Using nlohmann/json to build a small JSON object
nlohmann::json data = {
{"name", "Ada"},
{"language", "C++"},
{"awesome", true}
};
fmt::print("JSON output: {}\n", data.dump(2));
return 0;
}
Nothing exotic — just proof that both libraries actually work.
Step 6: The Magic Command That Ties It All Together
This is the part that trips people up, so pay attention. When you run CMake, you need to tell it about vcpkg using something called a toolchain file. Think of the toolchain file as a translator that tells CMake "hey, before you go looking for libraries, check vcpkg's fridge first."
cmake -B build -S . \
-DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake
Replace /path/to/vcpkg with wherever you cloned vcpkg in Step 1.
When you run this command, here's what actually happens behind the scenes:
- CMake starts up and sees the toolchain file.
- The toolchain file quietly tells vcpkg to look at your
vcpkg.json. - vcpkg sees you need
fmtandnlohmann-json. - vcpkg automatically downloads and builds those libraries (just for this project, in a local hidden folder called
vcpkg_installed/). - CMake then finds those freshly built libraries and links them into your app.
You didn't have to manually download anything. You just described what you wanted, and the tools handled the "how."
Then you actually build the project:
cmake --build build
And run it:
./build/my_cool_app
You should see:
Hello, World! You are 25 years old.
JSON output: {
"awesome": true,
"language": "C++",
"name": "Ada"
}
A Nice Shortcut: Skip the Long Command
Typing that long -DCMAKE_TOOLCHAIN_FILE=... path every time gets old fast. You can set an environment variable once instead:
export VCPKG_ROOT=/path/to/vcpkg
export PATH=$VCPKG_ROOT:$PATH
Then, in your CMakeLists.txt, you can even skip passing the flag manually if your CMake setup detects VCPKG_ROOT (many modern IDEs like Visual Studio and CLion do this automatically). Or you can use a CMakePresets.json file, which is the "grown-up" way of never typing that flag again:
{
"version": 6,
"configurePresets": [
{
"name": "default",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}
]
}
Now you can just run:
cmake --preset default
cmake --build build
Much cleaner.
Pinning Versions (So Things Don't Break Randomly)
By default, vcpkg.json grabs whatever's currently in vcpkg's catalog. If you want reproducible builds — meaning your teammate's computer installs the exact same library versions as yours — you add a vcpkg-configuration.json file that locks vcpkg to a specific snapshot in time (called a "baseline"):
{
"default-registry": {
"kind": "git",
"repository": "https://github.com/microsoft/vcpkg",
"baseline": "a1b2c3d4e5f6..."
}
}
You get that baseline hash by running git rev-parse HEAD inside your vcpkg folder. This just means "always use the library versions as they existed on this exact date," so nobody's build randomly changes six months from now.
Quick Recap (For Skimmers)
| Step | What You Do | What It's Like |
|---|---|---|
| 1 | Clone & bootstrap vcpkg once | Installing the grocery store app |
| 2 | Write vcpkg.json |
Writing your shopping list |
| 3 | Write CMakeLists.txt with find_package |
Telling your kitchen where ingredients go |
| 4 | Run CMake with the toolchain file | Sending your list to the store, groceries appear |
| 5 | Build & run | Cooking the meal |