Set Up a Modern C++ Project with CMake, vcpkg, and clang-tidy
Wire up dependency management and static analysis from scratch so every build is clean and lint-checked.
What you'll build
A minimal but functional C++ project that pulls in a third-party library (fmt) via vcpkg's manifest mode, builds with CMake, and runs clang-tidy automatically on every compile. The same commands work on all three major platforms.
Prerequisites
| Tool | Minimum version | Where to get it |
|---|---|---|
| CMake | 3.21 | cmake.org/download |
| Git | any recent | git-scm.com |
| C++ compiler | C++17-capable | see notes below |
| clang-tidy | 14+ | via LLVM |
Platform notes:
- macOS:
xcode-select --installgives you Apple Clang. Install clang-tidy separately withbrew install llvm, then add/opt/homebrew/opt/llvm/bin(Apple Silicon) or/usr/local/opt/llvm/bin(Intel) to yourPATH. - Linux (Debian/Ubuntu):
sudo apt install cmake clang clang-tidy; Fedora:sudo dnf install cmake clang clang-tools-extra. - Windows: Install Visual Studio 2022 with the "C++ CMake tools for Windows" and "C++ Clang tools for Windows" workload components, or install LLVM from llvm.org and CMake separately.
1. Install vcpkg
Clone it somewhere permanent on your machine. Do not put it inside your project.
git clone https://github.com/microsoft/vcpkg.git ~/vcpkg
cd ~/vcpkg
./bootstrap-vcpkg.sh # macOS / Linux
git clone https://github.com/microsoft/vcpkg.git $HOME\vcpkg
cd $HOME\vcpkg
.\bootstrap-vcpkg.bat # Windows PowerShell
Then expose VCPKG_ROOT so CMake can find the toolchain file. Add this to ~/.zshrc or ~/.bashrc:
export VCPKG_ROOT="$HOME/vcpkg"
On Windows, set VCPKG_ROOT as a User environment variable pointing to where you cloned vcpkg.
2. Create the project layout
myproject/
├── CMakeLists.txt
├── vcpkg.json
├── .clang-tidy
└── src/
└── main.cpp
mkdir -p myproject/src && cd myproject
3. Write the files
vcpkg.json is the manifest. vcpkg reads it during CMake configure and builds your dependencies automatically.
{
"name": "myproject",
"version": "0.1.0",
"dependencies": ["fmt"]
}
CMakeLists.txt:
cmake_minimum_required(VERSION 3.21)
project(myproject LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(fmt CONFIG REQUIRED)
add_executable(myproject src/main.cpp)
target_link_libraries(myproject PRIVATE fmt::fmt)
find_program(CLANG_TIDY_EXE NAMES clang-tidy)
if(CLANG_TIDY_EXE)
set_target_properties(myproject PROPERTIES
CXX_CLANG_TIDY "${CLANG_TIDY_EXE}")
endif()
The if(CLANG_TIDY_EXE) guard matters. Without it, CMake sets the variable to CLANG_TIDY_EXE-NOTFOUND when the tool is missing, and passing that string to CXX_CLANG_TIDY makes the build fail with a confusing "failed to run" error. The guard lets the build succeed without linting when clang-tidy isn't installed.
.clang-tidy controls which checks run. This is a reasonable starting set:
Checks: "clang-diagnostic-*,clang-analyzer-*,modernize-*,readability-simplify-boolean-expr"
WarningsAsErrors: "modernize-*"
src/main.cpp:
#include <fmt/core.h>
int main() {
fmt::print("Hello, {}!\n", "world");
return 0;
}
4. Configure and build
Pass the vcpkg toolchain file to CMake. This is what lets find_package locate vcpkg-installed libraries.
cmake -B build -S . \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
cmake -B build -S . `
-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
vcpkg downloads and compiles fmt during this step. Then build:
cmake --build build
Verify it works
Run the binary:
./build/myproject # macOS / Linux
.\build\Debug\myproject.exe # Windows
Expected output:
Hello, world!
During cmake --build, you should see clang-tidy running alongside the compiler. If a check fires, it prints the file, line number, and check name. modernize-* violations fail the build because of WarningsAsErrors.
Troubleshooting
Could not find a package configuration file provided by "fmt"
You forgot -DCMAKE_TOOLCHAIN_FILE. Delete the build/ directory (CMake cached the bad state) and re-run configure with the flag.
clang-tidy checks aren't running during build
If CMake couldn't find clang-tidy on PATH at configure time, it skips setting CXX_CLANG_TIDY and the build proceeds without linting. Check the CMake output for the resolved path. On macOS, confirm the Homebrew LLVM bin directory is in your PATH before configuring. On Linux: which clang-tidy. After fixing PATH, delete build/ and re-run configure.
vcpkg build fails or takes very long
vcpkg compiles packages from source on first use for a given platform triplet. This is expected and can take several minutes. Subsequent runs hit the binary cache (~/.cache/vcpkg on Linux/macOS, %LOCALAPPDATA%\vcpkg on Windows). Make sure you have a working internet connection and a few hundred MB free.
VCPKG_ROOT is empty at configure time
Environment variables set in your shell profile only apply to new shells. Open a fresh terminal after editing .zshrc or .bashrc, or run source ~/.zshrc to reload it.
Next steps
- Add a
CMakePresets.jsonto encode the toolchain path and build type into named presets, so you replace that long-Dflag withcmake --preset default. - Set up vcpkg binary caching for CI to avoid rebuilding dependencies from source on every pipeline run.
- Add a GitHub Actions workflow that configures, builds, and checks clang-tidy output so linting failures block merges.
- Look at
FetchContentfor header-only or CMake-native dependencies that aren't in the vcpkg registry yet.
Priya covers AI frameworks, developer productivity tooling, and the startup ecosystem across South and Southeast Asia, bringing a researcher's rigour and a practitioner's empathy to every story. She is deeply sceptical of benchmarks and asks hard questions so her readers don't have to.
Discussion 0
No comments yet
Be the first to weigh in.