C++ and CMake
- Description: Modern target-based CMake for C++ — projects, libraries, dependencies (
find_package,FetchContent), install/export, idioms and anti-patterns - My Notion Note ID: K2A-B2-1
- Created: 2020-01-13
- Updated: 2026-04-30
- License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io
Table of Contents
- 1. Overview
- 2. Minimal Project
- 3. Targets
- 4. Sources, Includes, Definitions, Options
- 5. Build Configuration
- 6. Finding and Pulling Dependencies
- 7. Build, Test, Install Commands
- 8. Install and Export
- 9. References
1. Overview
CMakeLists.txtdescribes build at high level;cmakereads + emits files for underlying tool — Makefiles, Ninja, VS, Xcode. Selected with-G <generator>. Same project targets any.- Modern CMake (3.x, esp. 3.15+) — target-centric: each target declares its own sources, headers, flags, deps; consumers inherit.
- Older directory-scoped style (
include_directories,add_definitions,link_directories) still supported but leaks across unrelated targets.
2. Minimal Project
cmake_minimum_required(VERSION 3.20)
project(myapp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) # use -std=c++20, not -std=gnu++20
add_executable(myapp main.cpp)
- Out-of-source build (only sane way):
cmake -S . -B build # configure (generate build files into build/)
cmake --build build -j # build using the underlying tool, parallel
./build/myapp # run
cmake_minimum_required— lowest version your code actually requires. Set to what you can realistically expect on contributor machines. Bumping later = breaking change for downstream packagers.
3. Targets
3.1 Executables, Libraries, Tests
add_executable(app main.cpp)
add_library(net STATIC net.cpp) # libnet.a
add_library(net SHARED net.cpp) # libnet.so / .dll
add_library(net OBJECT net.cpp) # object files only, no archive
add_library(net INTERFACE) # header-only; no compiled artifact
add_library(net) # type follows BUILD_SHARED_LIBS
# default: STATIC
target_link_libraries(app PRIVATE net)
- Tests via CTest:
include(CTest)
enable_testing()
add_executable(net_test net_test.cpp)
target_link_libraries(net_test PRIVATE net GTest::gtest_main)
add_test(NAME net_test COMMAND net_test)
- Run:
ctest --test-dir build --output-on-failure.
3.2 PUBLIC, PRIVATE, INTERFACE
target_link_libraries,target_include_directories,target_compile_definitions,target_compile_options,target_compile_features— all take visibility keyword:
| Keyword | Used by this target's sources? | Propagated to consumers that link this target? |
|---|---|---|
PRIVATE |
Yes | No |
INTERFACE |
No | Yes |
PUBLIC |
Yes | Yes |
-
Picking right one = single most important skill in modern CMake. Rules of thumb:
-
Implementation detail (used in
.cpponly) →PRIVATE. -
Appears in public header →
PUBLIC. -
Header-only library, no
.cpp→INTERFACEeverywhere. -
Wrong choice silently leaks deps up the graph (everyone linking you also links your private deps) or hides them (consumer can't see exposed header).
4. Sources, Includes, Definitions, Options
- Always attach things to a target, not a directory.
add_library(net
src/socket.cpp
src/dns.cpp
)
target_include_directories(net
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include # consumers see <net/socket.h>
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src # internal headers
)
target_compile_definitions(net
PUBLIC NET_VERSION=2
PRIVATE NET_INTERNAL_DEBUG=1
)
target_compile_options(net PRIVATE
-Wall -Wextra -Wpedantic
)
target_compile_features(net PUBLIC cxx_std_20) # cleaner than CMAKE_CXX_STANDARD
-
target_compile_featuresfor language standard at target level beatsCMAKE_CXX_STANDARDglobally — travels with target when consumed viaadd_subdirectoryorfind_package. -
Don't
file(GLOB)to collect sources. CMake won't notice when files added/removed without reconfigure. Either list.cppexplicitly (clear inventory ingit diff) orfile(GLOB CONFIGURE_DEPENDS ...)(configure-time scan on every build). -
Don't write
CMAKE_CXX_FLAGSfrom inside project. Don't bake-O2/-gyourself. Those belong to user's toolchain / build type — overwriting surprises packagers, breaks cross-compile. -
Use
target_compile_optionsfor target-specific flags; letCMAKE_BUILD_TYPEdrive optimization level. -
Include dirs needing different behavior in build tree vs install (common for libs) — generator expressions:
target_include_directories(net PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
5. Build Configuration
5.1 Standard, Generators, Build Types
cmake -S . -B build -G Ninja # use Ninja (recommended)
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release # single-config generators
cmake --build build --config Release # multi-config (VS, Xcode)
- Standard types:
Debug,Release,RelWithDebInfo,MinSizeRel. - Single-config (Make, Ninja) — type baked at configure time.
- Multi-config (VS, Xcode, "Ninja Multi-Config") — type picked at build time.
# Default to Release if the user didn't pick one (single-config only)
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
endif()
5.2 User Options
option(MYAPP_BUILD_TESTS "Build unit tests" ON)
option(MYAPP_USE_TBB "Enable TBB-based parallelism" OFF)
if(MYAPP_BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
- Pass at configure time:
cmake -S . -B build -DMYAPP_BUILD_TESTS=OFF -DMYAPP_USE_TBB=ON
- Prefix project options (
MYAPP_*) → no collision when consumed viaadd_subdirectory.
6. Finding and Pulling Dependencies
6.1 find_package
- For already-installed system libs:
find_package(Threads REQUIRED)
find_package(fmt 10 CONFIG REQUIRED)
find_package(Boost 1.81 REQUIRED COMPONENTS system filesystem)
target_link_libraries(app PRIVATE
Threads::Threads
fmt::fmt
Boost::system Boost::filesystem
)
- Locates package via lib's config file (
<pkg>Config.cmake) or CMake's Find-module (Find<pkg>.cmake). - Config mode preferred. Find-modules = legacy fallback.
- Always link imported target (
fmt::fmt), not raw${FMT_LIBRARIES}. Imported targets carry include dirs, flags, transitive deps.
6.2 FetchContent
- Pull dep at configure time, build as part of your project:
include(FetchContent)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(fmt)
target_link_libraries(app PRIVATE fmt::fmt)
FetchContent_MakeAvailable(CMake 3.14+) clones, thenadd_subdirectorys the dep, exposes its targets.
6.3 add_subdirectory
- For sibling subprojects in your monorepo:
add_subdirectory(libs/net)
add_subdirectory(apps/server)
target_link_libraries(server PRIVATE net)
7. Build, Test, Install Commands
# Configure
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
# Build (parallel; -j without a number uses all cores)
cmake --build build -j
# Build a specific target
cmake --build build --target net
# Test
ctest --test-dir build --output-on-failure -j
# Install
cmake --install build --prefix /usr/local
# Clean
cmake --build build --target clean
cmake --buildpreferred over callingmake/ninjadirectly — works regardless of generator.
8. Install and Export
- To make a lib findable via
find_packagefrom another project — install target and generate config file.
install(TARGETS net
EXPORT netTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
install(DIRECTORY include/ DESTINATION include)
install(EXPORT netTargets
FILE netTargets.cmake
NAMESPACE net::
DESTINATION lib/cmake/net
)
include(CMakePackageConfigHelpers)
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/cmake/netConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/netConfig.cmake
INSTALL_DESTINATION lib/cmake/net
)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/netConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/netConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/netConfigVersion.cmake
DESTINATION lib/cmake/net
)
- Downstream consumers:
find_package(net 2 CONFIG REQUIRED)
target_link_libraries(myapp PRIVATE net::net)
net::namespace = convention. Makes linker errors obvious —net::netis imported target, not system library.
9. References
- CMake official docs — language reference, command + variable list.
- Professional CMake by Craig Scott — practical reference for modern CMake.
- Effective Modern CMake (gist by Manuel Binna) — concise do's and don'ts.
- It's Time to Do CMake Right (Pablo Arias) — popularized target-centric approach.