CMake for Windows: A Concrete Protobuf Example in C++
On Windows for my version of CMake the find_package searches this folder: C:\Program Files\CMake\share\cmake-4.2\Modules This is where the CMake Protobuf definitions are!
"There is no abstract art. You must always start with something.” –Pablo Picasso

GitHub link:https://github.com/mday299/keypuncher/tree/main/C%2B%2B/CMake/cmakeWinProtoCxx
Introduction
I have been using CMake since about 2008. It has a pretty steep learning curve but I have learned to appreciate just how fast it can make development once you jump through some hoops. I have an article on CMake on this site at https://www.keypuncher.net/cmake-introduction/ but it is largely from a Linux/Ubuntu perspective.
This tutorial is intended to be Windows specific. As such it was done on Windows 11 and Git For Windows https://git-scm.com/install/windows.
Prerequisites
Visual Studio Community Edition: https://visualstudio.microsoft.com/vs/community/
- Important! Select “Desktop Development with C++” when downloading.
- Important! If installing for the first time a machine restart is required.
- Important if you are going to build with “PowerShell” and not “Developer PowerShell for VS” add the system or user environment variable to the PATH for nmake.exe, see https://stackoverflow.com/questions/63584921/getting-the-error-nmake-not-found-in-path-when-trying-to-create-migrate-r
Pros of CMake
- Cross-Platform (This is the reason I like it)
- Works on Windows, Linux, macOS, embedded systems
- Single build configuration for all platforms
- Industry Standard
- Used by major projects - https://llvm.org/ LLVM, https://opencv.org/OpenCV, https://www.qt.io/ Qt, https://www.boost.org/ Boost, etc.
- Large ecosystem and community support
- Supports multiple compilers (MSVC, GCC, Clang, etc.)
- Modular and Integration with Package Managers
- find_package() allows easy dependency management
- Transitive dependencies handled automatically with modern CMake
- Works well with https://vcpkg.io/en/ vcpkg, https://conan.io/ Conan, https://hunter.readthedocs.io/en/latest/ Hunter
- Generator Agnostic
- Can generate Visual Studio projects, Makefiles, Ninja, or Xcode projects
- Developers can use their preferred IDE/build system
Cons of CMake
I want to make clear at the outset that other build systems have similar quirks! Alternatives to CMake include Meson https://mesonbuild.com/, Bazel https://bazel.build/, and xmake https://xmake.io/.
CMake’s variable system is global by default, which means any variable you set in one part of your project can be silently changed somewhere else. The result is that a variable you thought controlled one thing may suddenly change because:
- a subdirectory redefines it
- a package’s
Find*.cmakefile sets it - the CMake cache contains an old value
- a typo creates a new variable instead of updating the one you meant
This is why CMake beginners often see “weird” behavior that magically disappears after deleting the build/ directory.
- Steep Learning Curve, Cryptic Error Messages
- Complex syntax; not quite a programming language, not quite declarative https://en.wikipedia.org/wiki/Declarative_programming
- Old vs modern CMake practices cause confusion
- Build System Leakage
- CMake abstracts build systems, but sometimes you need to know details.
When to Use CMake
- Need maximum portability between Linux, Windows, Mac, embedded, etc.
- Using established libraries: they probably have CMake support
Consider Alternatives if:
- Small, personal project. CMake might be overkill! That said, projects sometimes start small and grow into something that can justify CMake.
- Pure Windows development: Visual Studio or MSBuild might suffice.
Installation
Download CMake from the website: https://cmake.org/download/. At the time of this writing, they had the release candidate version listed first so I would scroll down to the actual version. Some people like to live on the edge and if that’s your thing I don’t judge. I’m using cmake-4.2.3-windows-x86_64.msi
Note that you must close any Powershells or cmd prompts that are BEFORE the CMake installation!
Run the installer as usual. This should result in CMake getting setup in your PATH and all the usual installation hullabaloo. It also should result in a link being placed in your start menu CMake (cmake-gui). If you like, click that link but: note that we are going to do everything from the Developer PowerShell for Visual Studio so the GUI won’t be covered in this tutorial. For the interested, scroll down to the bottom of the credits and you will find some links to online content covering that GUI.
To verify cmake is in your PATH go to a PowerShell or cmd.exe prompt and type:
cmakeIf you get a help menu you are golden. If not then CMake is probably not in your PATH. If that happens to be so, you can follow a tutorial online to append to the PATH environment variable for Windows.
At the time of this writing this portion of the tutorial was heavily inspired by https://github.com/Kitware/CMake/tree/master/Help/guide/tutorial/Step8
In Developer PowerShell for Visual Studio, cd into the directory:
cd <path-to>\Step8\then
cmake -B buildthis should produce a build for your compiler (presumably Visual Studio) in a folder called “build.”
To do the build:
cmake --build build --config DebugThis will place and executable file into the subdirectory or build\Tutorial\Debug\Tutorial.exe
The CMake tutorial just outputs the square root of the input. Run it like so:
.\build\Tutorial\Debug\Tutorial.exe 65Which produces output:
INFO: Computed sqrt of 65 to be 8.06225774829855 with SSE2
The square root of 65 is 8.06225774829855
The square of 8.06225774829855 is 64.99999999999999That’s all fine and good, but I promised at the outset that I would provide a way to build Protobuf files and I intend to do that.
CMake’s find_package()
On Windows for my version of CMake the find_package searches this folder:
C:\Program Files\CMake\share\cmake-4.2\Modules
In that folder there should be a file called FindProtobuf.cmake:
C:\Program Files\CMake\share\cmake-4.2\Modules> ls FindProtob*
Directory: C:\Program Files\CMake\share\cmake-4.2\Modules
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 1/27/2026 11:13 AM 39456 FindProtobuf.cmakeThis is where the CMake Protobuf definitions are!
Protoc Code Generator
Protobuf is notoriously hard to get working with CMake, especially on Windows. You can have conflicts between old protofbuf CMake and new Protobuf CMake and you can get missing transitive dependencies if you don’t use new CMake.
Under the hood what’s really being used by CMake is a code generator for various languages (it doesn’t help much that it is widely known as the protoc compiler: see https://protobuf.dev/reference/other/). The protoc code generator doesn’t generate to machine code, it generates code into supported languages like C++, C#, Python, Java, etc. Then you compile normally.
I’m using vcpkg from Microsoft which does most of that magic for me. The main downside to this approach is it is not cross-platform friendly. However I find it is easiest for beginners to wrap their heads around. For a more cross-platform version consider MSYS2: https://www.msys2.org/. That is not covered in this article, but lightly touched on in: https://www.keypuncher.net/g-with-msys-vs-code/
vcpkg
Download vcpkg from GitHub and cd into the directory. Note that the -disable metrics flag on the .bat file cuts down on Microsoft telemetry.
cd into wherever your GitHub repos reside.
git clone https://github.com/microsoft/vcpkg
cd vcpkg
.\bootstrap-vcpkg.bat -disableMetrics
.\vcpkg install protobuf:x64-windowsThis can take a while to build. After that:
.\vcpkg integrate installThis will produce lines like:
CMake projects should use: "-DCMAKE_TOOLCHAIN_FILE=C:/Users/mday39/repos/vcpkg/scripts/buildsystems/vcpkg.cmake"
All MSBuild C++ projects can now #include any installed libraries. Linking will be handled automatically. Installing new libraries will make them instantly available.Full Protobuf Example
Now you should be able to build using vcpkg. On my system my commands were as follows, but you WILL have to adjust for your username and repo location!
cd <path-to>\cmakeWinProtoCxx\CMakeProtobufExample
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE="C:/Users/<user-name>/repos/vcpkg/scripts/buildsystems/vcpkg.cmake"
cmake -B build-debug -S . `-DCMAKE_TOOLCHAIN_FILE="C:/Users/<user-name>/repos/vcpkg/scripts/buildsystems/vcpkg.cmake" ` -DCMAKE_BUILD_TYPE=Debug
cmake --build build-debug --config Debug
.\build-debug\Debug\ProtobufExample.exe
cmake -B build-release -S . `-DCMAKE_TOOLCHAIN_FILE="C:/Users/<username>/repos/vcpkg/scripts/buildsystems/vcpkg.cmake" ` -DCMAKE_BUILD_TYPE=Release
cmake --build build-release --config Release
.\build-release\Release\ProtobufExample.exeThis should result in a rudimentary library checkout system!
Why the add_book_request.bin file?
The add_book_request.bin file is a binary file created by the example main.cpp I provided earlier. It contains a serialized protobuf message.
Specifically, it's an AddBookRequest message (from your library.proto) that has been serialized to disk. Here's what the binary code roughly does:
// Create an AddBookRequest with a Book inside it
library::AddBookRequest addRequest;
addRequest.mutable_book()->CopyFrom(book);
// Serialize (save) it to a binary file
std::fstream output("add_book_request.bin", std::ios::out | std::ios::binary | std::ios::trunc);
addRequest.SerializeToOstream(&output);
output.close();
// Later, deserialize (load) it back
library::AddBookRequest loadedRequest;
std::fstream input("add_book_request.bin", std::ios::in | std::ios::binary);
loadedRequest.ParseFromIstream(&input);
input.close();Why is this useful?
- Persistence - Save data to disk and load it later
- Network transmission - Send binary data over the network (more efficient than JSON/XML)
- Inter-Process Communication (IPC) - Share data between different processes/applications
- Debugging - You can inspect what was serialized.
Conclusion
You’ve taken a full Windows‑native C++ project from a clean directory to a working Protobuf‑powered application using CMake and Visual Studio! Along the way, you saw how CMake discovers packages, how Visual Studio organizes multi‑target builds, and how vcpkg simplifies dependency management on Windows.
CMake can feel intimidating at first, especially on Windows where build systems and toolchains behave differently than on Linux. But once you understand how configuration, generators, and package discovery fit together, the workflow becomes predictable and fast. With this foundation in place, you can extend the example into larger systems, adapt the project for cross‑platform builds, and more!
Feedback
Credits
CMake website: https://cmake.org/
CMake GitHub: https://github.com/Kitware/CMake
CMake GitHub where the tutorials live: https://github.com/Kitware/CMake/tree/master/Help/guide/tutorial
CMake tutorial: https://cmake.org/cmake/help/latest/guide/tutorial/index.html
CMake GUI Tutorials: https://www.youtube.com/watch?v=0eTVeABMrzQ
and https://cs184.eecs.berkeley.edu/sp19/article/10/cmake-gui-windows-tutorial
and https://cmake.org/cmake/help/latest/manual/cmake-gui.1.html
Protocol Buffers Main Site: https://protobuf.dev/
Protocol Buffers GitHub: https://github.com/protocolbuffers/protobuf
Protocol Buffers Encoding: https://protobuf.dev/programming-guides/encoding/
Protocol Buffers C++ Reference: https://protobuf.dev/reference/cpp/api-docs/
Advanced Reading
JSON file from Step 8
CMakePresets.json is CMake’s way of capturing your build configuration in a clean, reproducible, IDE‑friendly format. Visual Studio, VS Code, CLion, and command‑line CMake all understand it, so it becomes the “source of truth” for how the project should be configured.
CmakePresets.json (NOTE THAT I HAVE MODIFIED THIS FILE FROM THE ORIGINAL ON Cmake’s web site). Main thing I did was make it work better with Visual Studio 2022.
{
"version": 4,
"configurePresets": [
{
"name": "default",
"displayName": "Default VS2022/VS2026",
"description": "Simple preset for Visual Studio Community",
"generator": "Visual Studio 17 2022",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"TUTORIAL_USE_STD_SQRT": "OFF",
"TUTORIAL_ENABLE_IPO": "OFF"
}
}
]
}Think of it as a CMake configure command encoded in JSON. Instead of relying on CMake’s defaults (which vary by platform, IDE, and environment), this file gives CMake a repeatable, predictable configuration.
For example, Instead of running:
cmake -S . -B build -DTUTORIAL_USE_STD_SQRT=OFF -DTUTORIAL_ENABLE_IPO=OFFYou can run:
cmake --preset tutorialOr in Visual Studio: Project → Configure Presets → tutorial
Breaking down the file
"version": 4
The schema version. Version 4 supports configure, build, and test presets.
"configurePresets": [...]
This section defines one or more configure presets.
"name": "default"
This is the internal name of the preset. Users can run it with:
cmake --preset default
The internal name. Used on the command line:
cmake --preset tutorial"displayName" and "description"
Purely for Integrated Development Euvironments (IDEs) like Visual Studio.
"generator": "Visual Studio 17 2022"
This is the most important line in the whole file.
It forces CMake to use the Visual Studio generator, which:
- enables multi‑config builds (Debug/Release)
- uses MSBuild instead of Ninja
- produces the standard Visual Studio directory layout
- works in both VS2022 and VS2026
Without this line, Visual Studio often defaults to Ninja.
"binaryDir": "${sourceDir}/build"
This tells CMake:
- Put all generated build files in
<source>/build ${sourceDir}is automatically the directory containing the preset file
"cacheVariables": {...}
These are the same as passing -DVAR=value on the command line.
I have:
TUTORIAL_USE_STD_SQRT=OFFUse your ownmysqrt()instead ofstd::sqrt.TUTORIAL_ENABLE_IPO=OFFDisable interprocedural optimization (LTO). The tutorial turns this on later.
These variables correspond directly to option() or set() commands in CMakeLists.
CMakePresets.json solves several real problems:
Keeps the file simple
Perfect for beginners — no inheritance, no build presets, no platform matrices.
Reproducibility
Everyone on the team configures the project the same way.
Guarantees Debug/Release separation
Because the Visual Studio generator is multi‑config.
Multiple configurations
You can add presets like:
- debug-with-sanitizers
- release-with-ipo
- cross-compile-arm
What This Built For Me
PS C:\Users\<username>\repos\keypuncher\C++\CMake\cmakeWinProtoCxx\Step8> ls build
Directory: C:\Users\<username>\repos\keypuncher\C++\CMake\cmakeWinProtoCxx\Step8\build
Mode LastWriteTime Length Name
d-— 3/3/2026 2:02 PM ALL_BUILD.dir
d-— 3/3/2026 2:02 PM CMakeFiles
d-— 3/3/2026 2:02 PM MathFunctions
d-— 3/3/2026 2:02 PM Tutorial
d-— 3/3/2026 2:02 PM ZERO_CHECK.dir
-a— 3/3/2026 2:02 PM 54085 ALL_BUILD.vcxproj
-a— 3/3/2026 2:02 PM 320 ALL_BUILD.vcxproj.filters
-a— 3/3/2026 2:02 PM 2453 cmake_install.cmake
-a— 3/3/2026 2:02 PM 14716 CMakeCache.txt
-a— 3/3/2026 2:02 PM 10185 Tutorial.sln
-a— 3/3/2026 2:02 PM 60661 ZERO_CHECK.vcxproj
-a— 3/3/2026 2:02 PM 563 ZERO_CHECK.vcxproj.filters
The files in the build/ directory are exactly what CMake generates when you target Visual Studio on Windows. If you are used to Unix Makefiles and Ninja (like) me this felt weird at first.
CMake has created a Visual Studio solution (Tutorial.sln) and a set of MSBuild project files (*.vcxproj) that represent each target in your CMakeLists. Visual Studio/MSBuild then uses those to orchestrate https://www.databricks.com/blog/what-is-orchestration the actual compilation.
The directories
CMakeFiles/
CMake’s internal bookkeeping. Contains:
- dependency info
- generated rules
- scripts for incremental builds
- metadata about each target
You never touch this typically.
ALL_BUILD.dir/ and ZERO_CHECK.dir/
These are MSBuild target directories created by Visual Studio generators.
ZERO_CHECKA special target that re-runs CMake automatically if any CMakeLists.txt changes. Visual Studio always generates this.ALL_BUILDA meta-target that builds everything in the solution.
These folders contain MSBuild’s intermediate files for those targets.
MathFunctions/ and Tutorial/
These correspond directly to your CMake targets:
add_subdirectory(MathFunctions)
add_executable(Tutorial tutorial.cxx)
Each gets its own:
.vcxprojfile- intermediate directory
- build rules
This is how Visual Studio organizes multi-target builds.
The files
Tutorial.sln
The Visual Studio solution file. This is the top-level container that groups all the .vcxproj projects.
*.vcxproj
MSBuild project files generated from your CMake targets.
ALL_BUILD.vcxproj— builds everythingZERO_CHECK.vcxproj— re-runs CMakeMathFunctions.vcxproj— your libraryTutorial.vcxproj— your executable
These are what Visual Studio actually builds.
*.vcxproj.filters
These define how files appear in the Visual Studio Solution Explorer (e.g., grouping .cpp and .h files). Purely UI metadata.
CMakeCache.txt
CMake’s configuration cache. Stores:
- compiler paths
- options
- detected libraries
- generator settings
This is why you can re-run builds without re-configuring.
cmake_install.cmake
The script CMake uses when you run:
cmake --install .Visual Studio generators produce a whole ecosystem of MSBuild projects because Visual Studio expects that structure.