C++ CMake Complete Guide | Cross-Platform Builds, CMake 3.28+, Presets & Modules
이 글의 핵심
Practical guide to CMake for C++: cross-platform builds, modern CMake 3.28+, presets, modules, and production patterns—with examples.
Troubleshooting: common CMake errors · Could NOT find ….
Introduction: “Build settings differ per platform—it’s hard to manage”
Common build pain points
When you develop C++ across platforms you often hit:
- Per-platform build files — separate
.vcxproj,Makefile, Xcode projects - Different tooling per teammate — Visual Studio, CLion, VS Code
- Dependency hell — Boost, OpenSSL, etc. installed in different paths
- Duplicated CI/CD — bespoke scripts in GitHub Actions, GitLab CI
- Slow builds — full rebuilds grow with project size
CMake addresses this:
# ❌ Existing method (management by platform)
# Windows: msbuild MyProject.vcxproj
# Linux: make -f Makefile.linux
# macOS: xcodebuild -project MyProject.xcodeproj
# ✅ CMake (write once, build anywhere)
mkdir build && cd build
cmake ..
cmake --build .
flowchart LR
subgraph input["Inputs"]
cmake["CMakeLists.txt"]
preset["CMakePresets.json (3.19+)"]
end
subgraph cmake_tool["CMake"]
gen["Generator"]
end
subgraph output["Outputs"]
make["Makefile (Linux)"]
vs["Visual Studio (Windows)"]
xcode["Xcode (macOS)"]
ninja["Ninja (all platforms)"]
end
cmake --> gen
preset --> gen
gen --> make
gen --> vs
gen --> xcode
gen --> ninja
```### Core CMake ideas
- **Meta build system**: CMake does not compile by itself; it generates platform build systems (Makefiles, `.sln`, etc.).
- **Targets**: Use `add_executable` / `add_library` and wire deps with `target_link_libraries`, `target_include_directories`.
- **Presets (3.19+)**: Share settings via `CMakePresets.json`.
- **C++20 modules (3.28+)**: First-class module support.
**Goals**:
- **CMake basics** (minimal setup, targets, dependencies)
- **CMake 3.28+** (presets, C++20 modules, FetchContent improvements)
- **Real-world patterns** (multi-platform, CI/CD, dependencies)
- **Performance** (parallel builds, ccache, PCH)
- **Common mistakes** and fixes
- **Production patterns**
**Requirements**: CMake 3.20+ (3.28+ recommended for latest features)
---
## Table of contents
1. [Problem scenarios](#problem-scenarios)
2. [Minimal CMakeLists.txt](#minimal)
3. [Targets: executables and libraries](#targets)
4. [CMake presets (3.19+)](#presets)
5. [Finding libraries: find_package](#find-package)
6. [C++20 modules (3.28+)](#cpp20-modules)
7. [Build types and compile flags](#build-types)
8. [Layout and subdirectories](#structure)
9. [Full example: multi-target project](#complete-example)
10. [Common issues and fixes](#common-errors)
11. [Performance](#performance)
12. [Best practices](#best-practices)
13. [Production patterns](#production-patterns)
14. [Summary and checklist](#summary)
---
## <a name="problem-scenarios"></a>1. Problem scenarios: build management pain
### Scenario 1: “Each team member has different build settings.”```text
Context: Windows developers use Visual Studio, Linux developers use Makefile, and macOS developers use Xcode.
Problem: Settings are not synchronized because each person manages different build files.
Outcome: “Does it build in my environment?” Problem occurred
→ Share unified build settings with CMake + CMakePresets.json```**Note:** “Works on my machine” usually means generators, flags, or paths were not committed—reproducible one-line presets matter more.
### Scenario 2: "The external library path varies depending on the environment."```text
Context: Boost is C:\boost on Windows, /usr/local/boost on Linux, and /opt/homebrew/boost on macOS.
Problem: Build fails in other environments due to hardcoded paths
Outcome: Each team member must edit CMakeLists.txt
→ Automatic detection with find_package + CMAKE_PREFIX_PATH```### Scenario 3: “Build is too slow”```text
Context: As the project grows, a full build takes 30 minutes.
Problem: Recompile all files and repeat header parsing every time.
Outcome: Decreased development productivity
→ Optimized with ccache, PCH, Ninja, and parallel builds```### Scenario 4: “Build setup in CI/CD is complicated”```text
Context: Different build scripts in GitHub Actions, GitLab CI, and Jenkins
Problem: All CI files need to be modified when changing build settings
Outcome: Increased maintenance burden
→ Unify CI settings with CMakePresets.json```
```mermaid
flowchart TB
subgraph Problems["Build management problems"]
P1[Per-platform build files]
P2[Mismatched dependency paths]
P3[Slow builds]
P4[Duplicated CI/CD config]
end
subgraph Solutions["CMake solutions"]
S1[Unified CMakeLists.txt]
S2[find_package discovery]
S3[ccache + Ninja + PCH]
S4[CMakePresets.json]
end
P1 --> S1
P2 --> S2
P3 --> S3
P4 --> S4
2. Minimal CMakeLists.txt
Hello World build
# CMakeLists.txt - CMake project file
# Minimum CMake version (3.20+)
cmake_minimum_required(VERSION 3.20)
# Project metadata (name, version, languages)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
# C++ standard
set(CMAKE_CXX_STANDARD 20) # Use C++20
set(CMAKE_CXX_STANDARD_REQUIRED ON) # Error if C++20 unavailable
set(CMAKE_CXX_EXTENSIONS OFF) # Disable GNU extensions (portability)
# Executable target
# myapp: target name (output name)
# main.cpp: source file
add_executable(myapp main.cpp)
// main.cpp
#include <iostream>
int main() {
std::cout << "Hello, CMake!" << std::endl;
return 0;
}
# Create build directory (out-of-source)
mkdir build
cd build
# Configure (generate build system)
cmake ..
# Build (compile with generated system)
cmake --build .
# Run
./myapp # Linux/macOS
# myapp.exe # Windows
Command cheat sheet
| Command | Meaning |
|---|---|
cmake_minimum_required(VERSION 3.20) | Minimum CMake version |
project(MyProject VERSION 1.0.0) | Project name and version |
set(CMAKE_CXX_STANDARD 20) | Use C++20 |
set(CMAKE_CXX_EXTENSIONS OFF) | Disable GNU extensions |
add_executable(myapp main.cpp) | Create executable target |
cmake .. | Configure from parent CMakeLists.txt |
cmake --build . | Portable build command |
Notable CMake versions
| Version | Highlights |
|---|---|
| 3.19 | CMakePresets.json |
| 3.20 | C++23 support |
| 3.23 | FILE_SET (headers) |
| 3.25 | LINUX variable |
| 3.26 | SYSTEM property tweaks |
| 3.28 | Native C++20 modules |
3. Targets: executables and libraries
Executables
# Single source
add_executable(myapp main.cpp)
# Multiple sources
add_executable(myapp
src/main.cpp
src/utils.cpp
src/config.cpp
)
# Using variables
set(SOURCES
src/main.cpp
src/utils.cpp
)
add_executable(myapp ${SOURCES})
Libraries
# Static library (.a / .lib)
add_library(mylib STATIC
src/lib.cpp
src/helper.cpp
)
# Shared library (.so / .dll)
add_library(mylib SHARED
src/lib.cpp
src/helper.cpp
)
# Header-only interface library
add_library(mylib INTERFACE)
target_include_directories(mylib INTERFACE include)
# Link into executable
add_executable(myapp src/main.cpp)
target_link_libraries(myapp PRIVATE mylib)
Note: For SHARED libraries, plan install/deploy rules (RUNTIME DESTINATION, etc.) so .so/.dll resolve at runtime.
PUBLIC vs PRIVATE vs INTERFACE
flowchart TB
subgraph LibA["Library A"]
A_PRIVATE["PRIVATE headers e.g. src/internal/"]
A_PUBLIC["PUBLIC headers e.g. include/"]
end
subgraph LibB["Library B"]
B_CODE[Lib B code]
end
subgraph App["Application"]
APP_CODE[App code]
end
A_PRIVATE -.->|visible in| LibA
A_PUBLIC -->|visible in| LibA
A_PUBLIC -->|visible to| LibB
A_PUBLIC -->|visible to| App
LibB -->|links| LibA
App -->|links| LibB
style A_PRIVATE fill:#FFB6C1
style A_PUBLIC fill:#90EE90
# PRIVATE: this target only
target_include_directories(mylib PRIVATE src/internal)
# PUBLIC: this target and dependents
target_include_directories(mylib PUBLIC include)
# INTERFACE: dependents only (typical header-only)
target_include_directories(mylib INTERFACE include)
```Example: Put `src/internal/impl.h` in `PRIVATE` if only `mylib` uses it; expose `include/mylib/api.h` as `PUBLIC`. After `myapp` links `mylib`, it sees only `PUBLIC` headers.
---
## <a name="presets"></a>4. CMake Presets (3.19+)
### What is a preset?
Problem: Different team members use different CMake commands.```bash
# Developer A
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=g++ ..
# Developer B
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang++ -DENABLE_TESTS=ON ..
# CI/CD
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=... ..
```Note: The method of copying and pasting a single long `cmake` line into a document is prone to breakage, so the purpose is to move it to the preset below.
SOLVED: Share build settings with `CMakePresets.json`.
### CMakePresets.json basic structure```json
{
"version": 6,
"cmakeMinimumRequired": {
"major": 3,
"minor": 25,
"patch": 0
},
"configurePresets": [
{
"name": "default",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_CXX_STANDARD": "20",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"displayName": "Debug Build",
"description": "Debug build with sanitizers",
"inherits": "default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_FLAGS": "-fsanitize=address,undefined"
}
},
{
"name": "release",
"displayName": "Release Build",
"description": "Optimized release build",
"inherits": "default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "windows-msvc",
"displayName": "Windows MSVC",
"inherits": "default",
"generator": "Visual Studio 17 2022",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
}
],
"buildPresets": [
{
"name": "debug",
"configurePreset": "debug",
"configuration": "Debug"
},
{
"name": "release",
"configurePreset": "release",
"configuration": "Release",
"jobs": 8
}
],
"testPresets": [
{
"name": "default",
"configurePreset": "debug",
"output": {
"outputOnFailure": true
}
}
]
}
```### How to use presets```bash
# Check available presets
cmake --list-presets
# Configured with presets
cmake --preset=debug
# Build with presets
cmake --build --preset=debug
# Test with presets
ctest --preset=default
```### Practical preset example: multi-platform```json
{
"version": 6,
"configurePresets": [
{
"name": "linux-gcc",
"displayName": "Linux GCC",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/linux-gcc",
"cacheVariables": {
"CMAKE_CXX_COMPILER": "g++",
"CMAKE_BUILD_TYPE": "Release"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
}
},
{
"name": "linux-clang",
"displayName": "Linux Clang",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/linux-clang",
"cacheVariables": {
"CMAKE_CXX_COMPILER": "clang++",
"CMAKE_BUILD_TYPE": "Release"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
}
},
{
"name": "macos",
"displayName": "macOS",
"generator": "Xcode",
"binaryDir": "${sourceDir}/build/macos",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
}
},
{
"name": "windows",
"displayName": "Windows MSVC",
"generator": "Visual Studio 17 2022",
"binaryDir": "${sourceDir}/build/windows",
"architecture": "x64",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
}
]
}
```Note: As you have more conditional presets, there will be confusion about “which preset is official” if you don’t pin the naming convention to your team document.
### Utilizing presets in CI/CD```yaml
# .github/workflows/build.yml
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
preset: [debug, release]
steps:
- uses: actions/checkout@v3
- name: Install CMake
uses: lukka/get-cmake@latest
- name: Configure
run: cmake --preset=${{ matrix.preset }}
- name: Build
run: cmake --build --preset=${{ matrix.preset }}
- name: Test
run: ctest --preset=default
```### User-specific presets (CMakeUserPresets.json)
Team shared settings are written in `CMakePresets.json` and personal settings are written in `CMakeUserPresets.json`.```json
// CMakeUserPresets.json (add to git ignore)
{
"version": 6,
"configurePresets": [
{
"name": "my-dev",
"inherits": "debug",
"cacheVariables": {
"CMAKE_PREFIX_PATH": "/home/myuser/local",
"ENABLE_EXPERIMENTAL_FEATURES": "ON"
}
}
]
}
```Note: It is better to place `CMakeUserPresets.json` in `.gitignore`, but also place an example file for team member onboarding (`CMakeUserPresets.json.example`).
---
## <a name="find-package"></a>5. Find external libraries: find_package
### Boost example```cmake
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE Boost::filesystem Boost::system)
```### OpenSSL example```cmake
find_package(OpenSSL REQUIRED)
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE OpenSSL::SSL OpenSSL::Crypto)
```Note: Mixing system OpenSSL and bundled OpenSSL may cause a runtime crash, so make sure to use the same build when deploying.
### Handling when search fails```cmake
find_package(SomeLib)
if(SomeLib_FOUND)
target_link_libraries(myapp PRIVATE SomeLib::SomeLib)
else()
message(WARNING "SomeLib not found, using fallback")
endif()
```### Using pkg-config```cmake
find_package(PkgConfig REQUIRED)
pkg_check_modules(CURL REQUIRED libcurl)
target_link_libraries(myapp PRIVATE ${CURL_LIBRARIES})
target_include_directories(myapp PRIVATE ${CURL_INCLUDE_DIRS})
```Note: Variable bases (`CURL_LIBRARIES`) have less propagation and tooling support than modern `IMPORTED` targets, so it is better to use the official CMake package or the `PkgConfig::libcurl` wrapper if possible.
### Automatic download of dependencies with FetchContent (3.11+)```cmake
include(FetchContent)
# Automatic download of GoogleTest
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
# Make it available
FetchContent_MakeAvailable(googletest)
# link
add_executable(tests test.cpp)
target_link_libraries(tests PRIVATE gtest_main)
```Related article: Using [vcpkg](/blog/cpp-series-53-3-vcpkg-basics/) or [Conan](/blog/cpp-series-53-4-conan-basics/) allows for more systematic package management.
3.24+ Enhancements: Remove timestamp warning with `DOWNLOAD_EXTRACT_TIMESTAMP` option.```cmake
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.0.0
DOWNLOAD_EXTRACT_TIMESTAMP ON # 3.24+
)
```Note: FetchContent is network dependent, so you will need to decide whether to use it in conjunction with the SBOM/Security Scan process.
---
## <a name="cpp20-modules"></a>6. C++20 module support (3.28+)
### What is a C++20 module?
Problems with existing header method:
- Parsing headers every time (slow compilation)
- Macro contamination
- Circular dependency problem
C++20 module benefits:
- Compile only once (fast build)
- Explicit export (clean interface)
- Avoid circular dependencies
### Using modules in CMake 3.28+```cmake
cmake_minimum_required(VERSION 3.28)
project(ModuleExample CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Enable module support
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
# Libraries (including modules)
add_library(mylib)
target_sources(mylib
PUBLIC
FILE_SET CXX_MODULES FILES
src/mymodule.cppm # Module interface file
PRIVATE
src/impl.cpp
)
# Run file
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)
```### Module file example```cpp
// src/mymodule.cppm (module interface)
export module mymodule;
export namespace mylib {
int add(int a, int b);
int multiply(int a, int b);
}
// src/impl.cpp (module implementation)
module mymodule;
namespace mylib {
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
}
```**Note:** Invalid `module` declaration in the implementation file will lead to link/visibility errors, so manage the interface and implementation pairs as a set.```cpp
// main.cpp (using module)
import mymodule;
#include <iostream>
int main() {
std::cout << mylib::add(2, 3) << std::endl; // 5
std::cout << mylib::multiply(4, 5) << std::endl; // 20
return 0;
}
```### Mixed use of module + header```cmake
add_library(mylib)
target_sources(mylib
PUBLIC
FILE_SET HEADERS FILES
include/legacy.h # legacy header
FILE_SET CXX_MODULES FILES
src/newmodule.cppm # new module
PRIVATE
src/legacy.cpp
src/newmodule.cpp
)
target_include_directories(mylib PUBLIC include)
```### Module support status by compiler (as of 2026)
| compiler | version | Module support |
|----------|------|-----------|
| GCC | 14+ | Fully supported |
| Clang | 17+ | Fully supported |
| MSVC | 19.36+ (VS 2022) | Fully supported |
---
## <a name="build-types"></a>7. Build type and compilation options
### Build type```cmake
# Set default values
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
# Flags for each build type
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -DDEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g")
set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG")
```**Note:** The multi-configuration generator (Visual Studio, Xcode) selects `--config` instead of `CMAKE_BUILD_TYPE`, so do not confuse it with this pattern.```bash
# Debug build
cmake -DCMAKE_BUILD_TYPE=Debug ..
# Release build
cmake -DCMAKE_BUILD_TYPE=Release ..
# Optimize including debug information
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
```### Target-specific compilation options```cmake
add_executable(myapp main.cpp)
# Enable warning
target_compile_options(myapp PRIVATE
-Wall -Wextra -Wpedantic
$<$<CONFIG:Debug>:-Werror> # Converts warnings to errors only in Debug.
)
# Preprocessing definitions
target_compile_definitions(myapp PRIVATE
APP_VERSION="1.0"
$<$<CONFIG:Debug>:DEBUG_MODE>
)
# header path
target_include_directories(myapp PRIVATE
${CMAKE_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/generated
)
Generator Expression
# Branch by build type
target_compile_options(myapp PRIVATE
$<$<CONFIG:Debug>:-O0 -g>
$<$<CONFIG:Release>:-O3>
)
# Compiler-specific branching
target_compile_options(myapp PRIVATE
$<$<CXX_COMPILER_ID:GNU>:-fno-exceptions>
$<$<CXX_COMPILER_ID:MSVC>:/EHsc>
)
```Note: Generator expressions are strings, so debugging is difficult. If they become complicated, split `target_compile_options` into multiple lines or wrap it in a custom property to ensure readability.
---
## <a name="structure"></a>8. Project structure and subdirectories
### Recommended directory structure```
project/
├── CMakeLists.txt # Root CMake settings
├── src/
│ ├── CMakeLists.txt # Source build settings
│ ├── main.cpp
│ └── lib/
│ ├── CMakeLists.txt
│ ├── lib.cpp
│ └── lib.h
├── include/ # public header
│ └── mylib/
│ └── api.h
├── tests/
│ ├── CMakeLists.txt
│ └── test_main.cpp
├── external/ # Submodule, external library
└── build/ # Build output (git ignore)```### root CMakeLists.txt```cmake
cmake_minimum_required(VERSION 3.20)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# options
option(BUILD_TESTS "Build tests" ON)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
# subdirectory
add_subdirectory(src)
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
src/CMakeLists.txt
# library
add_library(mylib
lib/lib.cpp
lib/lib.h
)
target_include_directories(mylib
PUBLIC ${CMAKE_SOURCE_DIR}/include
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
)
# Run file
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)
tests/CMakeLists.txt
find_package(GTest REQUIRED)
add_executable(tests
test_main.cpp
test_lib.cpp
)
target_link_libraries(tests PRIVATE
mylib
GTest::gtest
GTest::gtest_main
)
add_test(NAME MyTests COMMAND tests)
```---
## <a name="complete-example"></a>9. Complete example: multi-target project
### Issue 1: Using absolute paths
Cause: Hardcoding absolute paths breaks builds in other environments.```cmake
# ❌ Incorrect use
target_include_directories(myapp PRIVATE /usr/local/include)
# ✅ Correct usage: Using CMake variables
target_include_directories(myapp PRIVATE ${CMAKE_SOURCE_DIR}/include)
```Note: Always make sure you use source tree variables and not installed package paths.
### Issue 2: Building from source directory
Cause: Running `cmake .` from the source directory causes the build files to get mixed up with the source.```bash
# ❌ Incorrect use
cd /project
cmake .
# ✅ Correct usage: out-of-source build
mkdir build
cd build
cmake ..
```### Issue 3: Cache issues
Symptom: Changing CMake settings is not reflected.
Cause: Previous settings cached in `CMakeCache.txt`.```bash
# Solution 1: Clear cache
rm CMakeCache.txt
cmake ..
# Solution 2: Recreate the build directory
cd ..
rm -rf build
mkdir build && cd build
cmake ..
```Note: Please note any important `-D` flags or move them to a preset before deleting them.
### Issue 4: Link order
Cause: If the library linking order is incorrect, an undefined reference error occurs.```cmake
# ❌ Wrong order: lib1 depends on lib2, but lib2 comes after
target_link_libraries(myapp PRIVATE lib2 lib1)
# ✅ Correct order: dependent libraries first
target_link_libraries(myapp PRIVATE lib1 lib2)
```### Issue 5: PUBLIC/PRIVATE confusion
Symptom: Header not found, or unnecessary header exposed.```cmake
# ❌ Misuse: Internal header as PUBLIC
target_include_directories(mylib PUBLIC src/internal)
# ✅ Correct use
target_include_directories(mylib
PUBLIC include # exposed to the outside world
PRIVATE src/internal # Internal use only
)
```### Issue 6: find_package fails
Symptom: `Could not find package SomeLib`.
Cause: The library is not installed or is in a location that CMake cannot find.```bash
# Solution 1: Install package
sudo apt install libboost-dev # Linux
brew install boost # macOS
# Solution 2: Specify CMAKE_PREFIX_PATH
cmake -DCMAKE_PREFIX_PATH=/custom/install/path ..
# Solution 3: Specify the path manually
cmake -DBoost_ROOT=/usr/local/boost ..
```---
## <a name="common-errors"></a>10. Frequently occurring problems and solutions
### Pattern 1: Compile warnings as errors```cmake
function(enable_strict_warnings target)
if(MSVC)
target_compile_options(${target} PRIVATE /W4 /WX)
else()
target_compile_options(${target} PRIVATE
-Wall -Wextra -Wpedantic -Werror
)
endif()
endfunction()
add_executable(myapp main.cpp)
enable_strict_warnings(myapp)
```### Pattern 2: Post-processing by build type```cmake
# Remove symbols with strip when building release
if(CMAKE_BUILD_TYPE STREQUAL "Release")
add_custom_command(TARGET myapp POST_BUILD
COMMAND ${CMAKE_STRIP} $<TARGET_FILE:myapp>
)
endif()
```### Pattern 3: Installation Rules```cmake
# Run file installation
install(TARGETS myapp
RUNTIME DESTINATION bin
)
# Install library
install(TARGETS mylib
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
# Install header
install(DIRECTORY include/
DESTINATION include
)
# Install configuration file
install(FILES config.json
DESTINATION etc
)
# installation
cmake --build . --target install
# or
make install
```### Pattern 4: Generate version information```cmake
# version.h.in
#define APP_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define APP_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define APP_VERSION_PATCH @PROJECT_VERSION_PATCH@
# CMakeLists.txt
project(MyProject VERSION 1.2.3)
configure_file(version.h.in ${CMAKE_BINARY_DIR}/version.h)
target_include_directories(myapp PRIVATE ${CMAKE_BINARY_DIR})
```### Pattern 5: Conditional compilation```cmake
option(ENABLE_LOGGING "Enable logging" ON)
if(ENABLE_LOGGING)
target_compile_definitions(myapp PRIVATE ENABLE_LOGGING)
target_sources(myapp PRIVATE src/logger.cpp)
endif()
```### Pattern 6: Platform-specific sources```cmake
if(WIN32)
target_sources(myapp PRIVATE src/platform/windows.cpp)
elseif(APPLE)
target_sources(myapp PRIVATE src/platform/macos.cpp)
elseif(UNIX)
target_sources(myapp PRIVATE src/platform/linux.cpp)
endif()
```### Issue 6: find_package fails
Symptom: `Could not find package SomeLib`.
**Cause**: The library is not installed or is in a location that CMake cannot find.```bash
# Solution 1: Install package
sudo apt install libboost-dev # Linux
brew install boost # macOS
# Solution 2: Specify CMAKE_PREFIX_PATH
cmake -DCMAKE_PREFIX_PATH=/custom/install/path ..
# Solution 3: Specify the path manually
cmake -DBoost_ROOT=/usr/local/boost ..
```### Issue 7: Presets not working
Symptom: “No such preset” error when running `cmake --preset=debug`.
**Cause**: CMake version is lower than 3.19, or JSON syntax error.```bash
# Solution 1: Check CMake version
Requires cmake --version # 3.19 or higher
# Solution 2: JSON grammar verification
# Use VSCode or online JSON validator
# Solution 3: Check the preset list
cmake --list-presets
```---
## <a name="performance"></a>11. Performance Optimization
### Parallel build```bash
# Use all CPU cores
cmake --build . -j$(nproc)
# or make
make -j$(nproc)
```### Use Ninja
Ninja is a faster build system than Make.```bash
# Create a build system with Ninja
cmake -G Ninja ..
# Build
ninja
```### Compilation caching with ccache```cmake
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
endif()
```### Precompile Header (PCH)```cmake
target_precompile_headers(myapp PRIVATE
<iostream>
<vector>
<string>
)
```### Unity build (3.16+)
Reduce compilation time by merging multiple source files into one.```cmake
set(CMAKE_UNITY_BUILD ON)
set(CMAKE_UNITY_BUILD_BATCH_SIZE 16)
add_executable(myapp ${SOURCES})
```---
## <a name="best-practices"></a>12. Best practices/best practices
### 1. Target-based approach (Modern CMake)```cmake
# ❌ The old-fashioned way (global variables)
include_directories(include)
link_libraries(mylib)
add_definitions(-DDEBUG)
# ✅ Modern method (target-based)
target_include_directories(myapp PRIVATE include)
target_link_libraries(myapp PRIVATE mylib)
target_compile_definitions(myapp PRIVATE DEBUG)
```### 2. Utilize Generator Expression```cmake
# Flags for each build type
target_compile_options(myapp PRIVATE
$<$<CONFIG:Debug>:-O0 -g>
$<$<CONFIG:Release>:-O3 -DNDEBUG>
)
# Compiler-specific flags
target_compile_options(myapp PRIVATE
$<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
$<$<CXX_COMPILER_ID:Clang>:-Weverything>
)
# Links by platform
target_link_libraries(myapp PRIVATE
$<$<PLATFORM_ID:Linux>:pthread>
$<$<PLATFORM_ID:Windows>:ws2_32>
)
```### 3. Utilizing the interface library```cmake
# Common settings to interface library
add_library(common_settings INTERFACE)
target_compile_features(common_settings INTERFACE cxx_std_20)
target_compile_options(common_settings INTERFACE
$<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra -Wpedantic>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
)
# Apply to all targets
target_link_libraries(myapp PRIVATE common_settings)
target_link_libraries(mylib PRIVATE common_settings)
```### 4. Automatic generation of version information```cmake
# version.h.in
#pragma once
#define PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define PROJECT_VERSION "@PROJECT_VERSION@"
# CMakeLists.txt
project(MyProject VERSION 1.2.3)
configure_file(
${CMAKE_SOURCE_DIR}/version.h.in
${CMAKE_BINARY_DIR}/generated/version.h
@ONLY
)
target_include_directories(myapp PRIVATE ${CMAKE_BINARY_DIR}/generated)
```### 5. Clarification of installation rules```cmake
# Run file installation
install(TARGETS myapp
RUNTIME DESTINATION bin
)
# Install library
install(TARGETS mylib
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
)
# Install headers (using FILE_SET, 3.23+)
install(TARGETS mylib
FILE_SET HEADERS DESTINATION include
)
# Create Config file (can find_package from other projects)
install(EXPORT MyLibTargets
FILE MyLibTargets.cmake
NAMESPACE MyLib::
DESTINATION lib/cmake/MyLib
)
```---
## <a name="production-patterns"></a>13. production pattern
### Pattern 1: Conditional feature activation```cmake
option(ENABLE_LOGGING "Enable logging" ON)
option(ENABLE_PROFILING "Enable profiling" OFF)
option(BUILD_EXAMPLES "Build examples" OFF)
if(ENABLE_LOGGING)
target_compile_definitions(myapp PRIVATE ENABLE_LOGGING)
target_sources(myapp PRIVATE src/logger.cpp)
endif()
if(ENABLE_PROFILING)
target_link_libraries(myapp PRIVATE profiler)
endif()
if(BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
```### Pattern 2: Platform-specific source management```cmake
# common source
set(COMMON_SOURCES
src/main.cpp
src/core.cpp
)
# Platform-specific sources
if(WIN32)
list(APPEND PLATFORM_SOURCES src/platform/windows.cpp)
elseif(APPLE)
list(APPEND PLATFORM_SOURCES src/platform/macos.cpp)
elseif(UNIX)
list(APPEND PLATFORM_SOURCES src/platform/linux.cpp)
endif()
add_executable(myapp ${COMMON_SOURCES} ${PLATFORM_SOURCES})
```### Pattern 3: Compile warnings as errors (CI/CD)```cmake
function(enable_warnings_as_errors target)
if(MSVC)
target_compile_options(${target} PRIVATE /W4 /WX)
else()
target_compile_options(${target} PRIVATE
-Wall -Wextra -Wpedantic -Werror
)
endif()
endfunction()
# Activated only in CI environment
if(DEFINED ENV{CI})
enable_warnings_as_errors(myapp)
endif()
```### Pattern 4: Post-processing by build type```cmake
# Strip when building Release
if(CMAKE_BUILD_TYPE STREQUAL "Release" AND UNIX)
add_custom_command(TARGET myapp POST_BUILD
COMMAND ${CMAKE_STRIP} --strip-all $<TARGET_FILE:myapp>
COMMENT "Stripping binary"
)
endif()
# Print build information
add_custom_command(TARGET myapp POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Build complete: $<TARGET_FILE:myapp>"
COMMAND ${CMAKE_COMMAND} -E echo "Build type: ${CMAKE_BUILD_TYPE}"
)
```### Pattern 5: Fix dependency versions```cmake
include(FetchContent)
# Manage with version variable
set(GOOGLETEST_VERSION "1.14.0")
set(FMT_VERSION "10.0.0")
set(SPDLOG_VERSION "1.12.0")
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v${GOOGLETEST_VERSION}
GIT_SHALLOW ON # Download only the latest commits
)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG ${FMT_VERSION}
GIT_SHALLOW ON
)
FetchContent_MakeAvailable(googletest fmt)
```### Pattern 6: Developer Mode```cmake
option(DEVELOPER_MODE "Enable developer mode" OFF)
if(DEVELOPER_MODE)
# Enable all warnings
add_compile_options(-Wall -Wextra -Wpedantic)
# Enable Sanitizer
add_compile_options(-fsanitize=address,undefined)
add_link_options(-fsanitize=address,undefined)
# Export compilation commands (used by clangd, ccls, etc.)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# test build
set(BUILD_TESTS ON)
endif()
```---
## <a name="summary"></a>14. Organize and Checklist
### Summary of CMake key concepts
| concept | Description | Example |
|------|------|------|
| Target | Build artifacts (executable files, libraries) | `add_executable`, `add_library` |
| Attribute | Target-specific settings | `target_include_directories` |
| Dependency | Link relationship between targets | `target_link_libraries` |
| Preset | Reusable Build Settings | `CMakePresets.json` |
| Generator | Creating platform-specific build systems | Ninja, Make, Visual Studio |
### CMake adoption checklist```bash
# ✅ Default settings
- [ ] Install CMake 3.20 or higher (3.28+ recommended)
- [ ] Use out-of-source build (build/ directory)
- [ ] C++ standard settings (CMAKE_CXX_STANDARD)
- [ ] Set CMAKE_CXX_EXTENSIONS OFF
# ✅ Target management
- [ ] Target-based approach (add_executable, add_library)
- [ ] PUBLIC/PRIVATE/INTERFACE clearly distinguished
- Use [ ] target_* commands (avoid global commands)
# ✅ Dependency management
- Find external libraries with [ ] find_package
- [ ] Automatic download with FetchContent (optional)
- [ ] Version fixation (reproducible build)
# ✅ Build settings
- [ ] Create CMakePresets.json (shared with team)
- [ ] Build type setting (Debug/Release)
- [ ] Enable warning (-Wall -Wextra or /W4)
- [ ] Turn warnings into errors in CI/CD (-Werror)
# ✅ Test
- [ ] enable_testing() + add_test()
- [ ] CTest integration
# ✅ Installation and Deployment
- Create [ ] install() rule
- [ ] Config file creation (find_package support)
# ✅Performance
- [ ] Enable parallel builds (-j)
- [ ] Consider using Ninja
- [ ] ccache settings
- [ ] PCH utilization (big project)
# ✅ Latest features (optional)
- [ ] C++20 module support (3.28+)
- [ ] Use FILE_SET (3.23+)
- [ ] Unity build (3.16+)
```### Recommended features by version
| CMake version | Recommendations |
|------------|-----------|
| 3.20+ | Basic features available |
| 3.23+ | Header management with FILE_SET |
| 3.25+ | Using LINUX variables |
| 3.28+ | C++20 module support |
### Practical Tips: Using CMake Efficiently
1. **Maintain multiple build directories**```bash
# Maintain debug, release, and profiling simultaneously
cmake --preset=debug
cmake --preset=release
cmake --preset=profile
# Build each when needed
cmake --build build/debug
cmake --build build/release
```2. **Export compile command (for LSP server)**```cmake
# Create compile_commands.json (used by clangd, ccls, etc.)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Create a symbolic link at the root
# ln -s build/compile_commands.json .
```3. **Measuring Build Time**```bash
# Measure build time
time cmake --build build
# or CMake built-in function
cmake --build build --verbose
```4. **Selective build by target**```bash
# Build only specific targets (no full build required)
cmake --build build --target mylib
cmake --build build --target tests
# Build multiple targets simultaneously
cmake --build build --target mylib --target myapp
```5. **Check cache variables**```bash
# Check current cache variable
cmake -L build
# Check advanced variables
cmake -LA build
# Check specific variables
cmake -L build | grep CMAKE_CXX_COMPILER
```### Quick reference: CMake command cheat sheet```bash
# 🏗️ Project settings
cmake -S . -B build # source: current, build: build/
cmake --preset=debug # Use preset (3.19+)
cmake -DCMAKE_BUILD_TYPE=Release # Specify build type
# 🔨Build
cmake --build build # Run build
cmake --build build -j8 # parallel build (8 cores)
cmake --build build --target myapp # specific target only
# 🧪Test
ctest --test-dir build # Run tests
ctest --preset=default # Test with preset
# 📦 Install
cmake --install build # Run installation
cmake --install build --prefix /usr/local # Specify path
# 🧹 Organization
cmake --build build --target clean # Delete build results
rm -rf build # complete initialization
```### Troubleshooting: Quick problem solving
| Symptoms | Cause | Solution |
|------|------|--------|
| Could not find package | Library not installed | Specify `apt install`, `brew install`, or CMAKE_PREFIX_PATH |
| Undefined reference | Link order error | Check target_link_libraries order |
| Header not found | Missing include path | add target_include_directories |
| Cache issues | CMakeCache.txt Outdated | `rm CMakeCache.txt` or `cmake --fresh` |
| No presets | CMake version low | CMake 3.19+ upgrade |
| Slow build | Use Make | Switch to Ninja (`-G Ninja`) |
### Build system performance comparison
| build system | speed | Parallelization | cross platform | learning curve |
|-------------|------|--------|---------------|-----------|
| Make | Normal | LIMITED | Linux/macOS | Easy |
| Ninja | Fast | Excellent | All platforms | Easy |
| Visual Studio | Normal | Excellent | Windows | Easy |
| Xcode | Normal | Excellent | macOS | middle |
Recommended: Ninja (fastest and supports all platforms)
### Next steps
- Detailed study of target properties in [CMake Targets Advanced](/blog/cpp-cmake-targets/)
- In-depth dependency management in [Find_package Complete Guide](/blog/cpp-cmake-find-package/)
- CMake vs other tools in [Build System Comparison](/blog/cpp-series-53-6-build-system-comparison/)
- Learn how to use the package manager in [vcpkg basics](/blog/cpp-series-53-3-vcpkg-basics/)
- Learn about another package manager in [Conan Basics](/blog/cpp-series-53-4-conan-basics/)
- Build automated builds in [CI/CD with GitHub Actions](/blog/cpp-series-40-2-cicd-github-actions/)
---
## FAQ
### Q1: What is the difference between CMake and Make?
**A**: **CMake** is a tool that **creates** a build system (meta build system), and **Make** is a tool that **performs** the build. When CMake creates a Makefile, Make reads that Makefile and compiles it.```
CMakeLists.txt → [CMake] → Makefile → [Make] → Executable file```### Q2: What is out-of-source build?
A: Separate source and build directories. If you create a `build/` directory and run `cmake ..` inside it, the build files will be generated only in `build/`, keeping your source clean.```bash
# ✅ Correct way
mkdir build && cd build
cmake ..
# ❌ Wrong method (mixing source and build files)
cmake .
```### Q3: When should I use PUBLIC vs PRIVATE vs INTERFACE?
A:
- PRIVATE: only used inside this target (implementation detail)
- PUBLIC: Use this target + targets linking this target as well (public API)
- INTERFACE: Use only targets that link to this target (header-only library)```cmake
# internal header
target_include_directories(mylib PRIVATE src/internal)
# public header
target_include_directories(mylib PUBLIC include)
# Header-only interface library
target_include_directories(header_only INTERFACE include)
```### Q4: When to use CMakePresets.json?
A: Used when sharing build settings in a team project or CI/CD. Personal settings are written in `CMakeUserPresets.json` and added to git ignore.```bash
# Without presets (complicated)
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=g++ -DENABLE_TESTS=ON ..
# Use presets (simple)
cmake --preset=debug
```### Q5: What if find_package fails?
A:
1. Verify library installation
2. Specify `CMAKE_PREFIX_PATH`
3. Use package manager (vcpkg, conan)```bash
# 1. Installation
sudo apt install libboost-dev # Linux
brew install boost # macOS
# 2. Specify the route
cmake -DCMAKE_PREFIX_PATH=/usr/local ..
# 3. Use vcpkg
cmake -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg.cmake ..
```### Q6: Do I need to use the C++20 module?
A: As of 2026, all major compilers (GCC 14+, Clang 17+, MSVC 19.36+) support it and its use is recommended for new projects. Migrate existing projects gradually.
Advantages: Fast compilation, clean interface, avoids circular dependencies.
Disadvantage: Increased build system complexity, caution required when mixed with legacy code
### Q7: The build is slow. How do I optimize it?
A:
1. Using Ninja (`cmake -G Ninja`)
2. **Parallel build** (`cmake --build . -j$(nproc)`)
3. Enable **ccache**
4. Use **PCH** (precompile header)
5. Consider **Unity Build** (large projects)```cmake
# enable ccache
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
endif()
# Unity build
set(CMAKE_UNITY_BUILD ON)
```### Q8: What are CMake learning resources?
A:
- [CMake official documentation](https://cmake.org/documentation/) (Check the latest features)
- "Professional CMake: A Practical Guide" (practical patterns)
- [CMake Tutorial](https://cmake.org/cmake/help/latest/guide/tutorial/index.html) (step-by-step learning)
- [Effective Modern CMake](https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1) (Best Practice)
### Q9: How to migrate an existing Makefile project to CMake?
A:
Migrate gradually, step by step.```cmake
# Step 1: Create a minimal CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(MyProject)
file(GLOB_RECURSE SOURCES "src/*.cpp") # Temporarily use GLOB
add_executable(myapp ${SOURCES})
# Step 2: Explicitly list source files
set(SOURCES
src/main.cpp
src/module1.cpp
# ...
)
# Step 3: Separate by target
add_library(mylib src/lib.cpp)
add_executable(myapp src/main.cpp)
target_link_libraries(myapp PRIVATE mylib)
# Step 4: Structuring subdirectories
add_subdirectory(src)
add_subdirectory(tests)
```### Q10: How to completely reset the CMake cache?
A:```bash
# Method 1: Delete the build directory (most obvious)
rm -rf build
mkdir build && cd build
cmake ..
# Method 2: Delete only CMakeCache.txt
rm CMakeCache.txt
cmake ..
# Method 3: Initialization with CMake command
cmake --fresh .. # CMake 3.24+
```### Q11: How to maintain multiple build types simultaneously?
A:
Separate build directories or use presets.```bash
# Method 1: Separate directories
mkdir build-debug && cd build-debug
cmake -DCMAKE_BUILD_TYPE=Debug ..
mkdir build-release && cd build-release
cmake -DCMAKE_BUILD_TYPE=Release ..
# Method 2: Use presets (recommended)
cmake --preset=debug
cmake --preset=release
# Generated in build/debug and build/release respectively
```### Q12: How to include Git commit hashes in versions in CMake?
A:```cmake
# Get Git commit hash
execute_process(
COMMAND git rev-parse --short HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Create version header
configure_file(
${CMAKE_SOURCE_DIR}/version.h.in
${CMAKE_BINARY_DIR}/version.h
)
# version.h.in
#define VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define VERSION_MINOR @PROJECT_VERSION_MINOR@
#define GIT_COMMIT "@GIT_COMMIT_HASH@"
```---
**One-line summary**: CMake 3.28+ allows you to efficiently build cross-platform projects including C++20 modules. Share team settings and take advantage of the latest features to increase productivity with CMakePresets.json.
---
## Good article to read together (internal link)
Here's another article related to this topic.
- [C++ CMake Targets Complete Guide | Target-based build system](/blog/cpp-cmake-targets/)
- [C++ CMake find_package complete guide | External library integration](/blog/cpp-cmake-find-package/)
- [C++ Conan Complete Guide | Modern C++ package management](/blog/cpp-conan/)
- [C++ vcpkg complete guide | Microsoft C++ Package Manager](/blog/cpp-vcpkg/)
- [Complete comparison of C++ build systems | CMake·Meson·Bazel·Makefile·Package Manager Selection Guide](/blog/cpp-series-53-6-build-system-comparison/)
## Practical checklist
This is what you need to check when applying this concept in practice.
### Before writing code
- [ ] Is this technique the best way to solve the current problem?
- [ ] Can team members understand and maintain this code?
- [ ] Does it meet the performance requirements?
### Writing code
- [ ] Have you resolved all compiler warnings?
- [ ] Have you considered edge cases?
- [ ] Is error handling appropriate?
### When reviewing code
- [ ] Is the intent of the code clear?
- [ ] Are there enough test cases?
- [ ] Is it documented?
Use this checklist to reduce mistakes and improve code quality.
---
## Keywords covered in this article (related search terms)
This article will be helpful if you search for C++, cmake, build, makefile, tools, cross-platform, cmake-presets, modules, etc.
---
## Related articles
- [C++ CMake find_package complete guide | External library integration](/blog/cpp-cmake-find-package/)
- [C++ CMake Targets Complete Guide | Target-based build system](/blog/cpp-cmake-targets/)
- [C++ Conan Complete Guide | Modern C++ package management](/blog/cpp-conan/)
- [C++ Makefile | ](/blog/cpp-makefile/)
- [C++ Benchmarking | ](/blog/cpp-benchmarking/)