πComplete Guide to Modern CMake Configuration
Table of Contents
Basic Project Configuration
C++ Standard Settings
Compiler Options and Warning Configuration
Output Directory Structure Management
Environment Detection and Path Configuration
Third-Party Dependency Management
Target Linking and Visibility
Installation Configuration and Deployment
Custom Build Targets
Complete Example Project
Basic Project Configuration
Minimum Version Requirement and Project Declaration
cmake_minimum_required(VERSION 3.14)
project(VisionDemo VERSION 1.0.0 LANGUAGES CXX)
Description:
<span>cmake_minimum_required</span>: Specifies the minimum required CMake version<span>project</span>: Defines the project name, version number, and programming languages used<span>VERSION</span>: Sets the project version, which can be referenced using<span>${PROJECT_VERSION}</span><span>LANGUAGES</span>: Explicitly specifies the programming languages used
C++ Standard Settings
Global Standard Settings
# Set C++17 standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
Parameter Meanings:
| Parameter | Function | Description |
|---|---|---|
<span>CMAKE_CXX_STANDARD</span> |
Specifies the C++ standard version | Tells the compiler which C++ standard to use (e.g., 17, 20, 23) |
<span>CMAKE_CXX_STANDARD_REQUIRED</span> |
Enforces the specified standard | ON: The compiler must support the specified standard, otherwise an error is raised |
<span>CMAKE_CXX_EXTENSIONS</span> |
Disables compiler extensions | OFF: Use pure standard C++, improving cross-platform compatibility |
Compiler Parameter Mapping:
- GCC/Clang:
<span>-std=c++17</span> - MSVC:
<span>/std:c++17</span>
Standard Settings for Specific Targets
set_target_properties(VisionDemo PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
)
Usage Scenarios:
- Different targets in the project require different C++ standards
- Explicitly specify the compilation standard for a specific target
- Override global settings
Compiler Options and Warning Configuration
Global Compiler Options
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
Target-Specific Compiler Options (Recommended)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(VisionDemo PRIVATE
-Wall
-Wextra
-Wpedantic
-Wno-unused-parameter
-Wno-unused-variable
)
endif()
Compiler Option Descriptions:
| Option | Function |
|---|---|
<span>-Wall</span> |
Enables all common warnings |
<span>-Wextra</span> |
Enables more additional warnings |
<span>-Wpedantic</span> |
Forces standard compliance checks |
<span>-Wno-unused-parameter</span> |
Disables unused parameter warnings |
<span>-Wno-unused-variable</span> |
Disables unused variable warnings |
Multi-Compiler Support
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(${TARGET_NAME} PRIVATE
-Wall -Wextra -Wpedantic
-Wno-unused-parameter -Wno-unused-variable
)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_compile_options(${TARGET_NAME} PRIVATE
/W4 # High warning level
/WX # Treat warnings as errors
/utf-8 # Use UTF-8 encoding
)
endif()
Important Reminder:<span>target_compile_options</span> must be called after <span>add_executable</span> or <span>add_library</span>, otherwise an error will occur:
Cannot specify compile options for target "TargetName" which is not built by this project.
Output Directory Structure Management
Build Phase Output Directory
# Set output directories for the build phase
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
Directory Type Descriptions:
| Variable | File Type | Example |
|---|---|---|
<span>CMAKE_RUNTIME_OUTPUT_DIRECTORY</span> |
Executable Files | <span>.exe</span>, Binary files without extension |
<span>CMAKE_LIBRARY_OUTPUT_DIRECTORY</span> |
Dynamic Link Libraries | <span>.so</span>, <span>.dylib</span>, <span>.dll</span> |
<span>CMAKE_ARCHIVE_OUTPUT_DIRECTORY</span> |
Static Libraries | <span>.a</span>, <span>.lib</span> |
Multi-Configuration Generator Support
# Specify output paths for each build type for multi-configuration generators (e.g., MSVC)
foreach(OUTPUT_CONFIG IN LISTS CMAKE_CONFIGURATION_TYPES)
string(TOUPPER ${OUTPUT_CONFIG} OUTPUT_CONFIG)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUT_CONFIG} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUT_CONFIG} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUT_CONFIG} ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY})
endforeach()
Build Result Example:
build/
βββ bin/
β βββ VisionDemo
βββ lib/
βββ libVisionDemo.a
βββ libVisionDemo.so
Environment Detection and Path Configuration
Anaconda Environment Detection
# Check if in Anaconda environment
if(DEFINED ENV{CONDA_PREFIX})
set(CMAKE_PREFIX_PATH $ENV{CONDA_PREFIX})
set(CMAKE_INCLUDE_PATH $ENV{CONDA_PREFIX}/include)
set(CMAKE_LIBRARY_PATH $ENV{CONDA_PREFIX}/lib)
message(STATUS "Detected Conda environment: $ENV{CONDA_PREFIX}")
endif()
Environment Variable Descriptions:
<span>CONDA_PREFIX</span>: Path of the currently activated Conda environment<span>CMAKE_PREFIX_PATH</span>: Prefix for CMake package search paths<span>CMAKE_INCLUDE_PATH</span>: Header file search paths<span>CMAKE_LIBRARY_PATH</span>: Library file search paths
Path Priority Management
# Set search path priority
list(INSERT CMAKE_PREFIX_PATH 0 ${CMAKE_CURRENT_SOURCE_DIR}/extern)
list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
Third-Party Dependency Management
Using FetchContent to Automatically Download Dependencies
include(FetchContent)
# Download and build fmt library
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 9.1.0
)
FetchContent_MakeAvailable(fmt)
# Download and build spdlog library
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.11.0
)
FetchContent_MakeAvailable(spdlog)
Advantages of FetchContent:
- π¦ No need to manually install third-party libraries
- π Can specify versions to avoid compatibility issues
- π Supports caching and incremental updates
- π§© Seamlessly integrates with CMake dependency management
Common Third-Party Library Configurations
# Google Test
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.12.1
)
FetchContent_MakeAvailable(googletest)
# JSON library
FetchContent_Declare(
nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.2
)
FetchContent_MakeAvailable(nlohmann_json)
# Eigen math library
FetchContent_Declare(
eigen
GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
GIT_TAG 3.4.0
)
FetchContent_MakeAvailable(eigen)
Using find_package Method
# Find system-installed packages
find_package(OpenCV REQUIRED)
find_package(Boost REQUIRED COMPONENTS system filesystem thread)
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
Target Linking and Visibility
Link Visibility Modifiers
target_link_libraries(MyTarget
PRIVATE privately_used_lib # Only used by the current target
PUBLIC publicly_exposed_lib # Used by both the current target and its dependents
INTERFACE interface_only_lib # Only used by dependents
)
Visibility Comparison:
| Modifier | Affects Current Target | Propagates to Dependents | Usage Scenario |
|---|---|---|---|
<span>PRIVATE</span> |
β | β | Internal implementation dependency |
<span>PUBLIC</span> |
β | β | Part of the public API |
<span>INTERFACE</span> |
β | β | Header-only library, interface library |
Practical Application Example
# Create library
add_library(MyLib src/mylib.cpp)
target_link_libraries(MyLib
PRIVATE spdlog::spdlog # Internal logging, not exposed
PUBLIC fmt::fmt # Public interface using fmt formatting
INTERFACE Eigen3::Eigen # Header-only library, only needed by dependents
)
# Create application
add_executable(MyApp src/main.cpp)
target_link_libraries(MyApp PRIVATE MyLib)
# MyApp can use fmt::fmt and Eigen, but cannot directly use spdlog
Creating Interface Libraries
# Create pure header-only library
add_library(MyHeaderLib INTERFACE)
target_include_directories(MyHeaderLib INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_link_libraries(MyHeaderLib INTERFACE fmt::fmt)
Installation Configuration and Deployment
Basic Installation Configuration
include(GNUInstallDirs) # Provides standard installation paths
# Install executable files
install(TARGETS VisionDemo
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
# Install library files
install(TARGETS VisionDemo
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} # Windows DLL
)
Complete Installation Configuration
# Install header files
install(DIRECTORY include/VisionDemo
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp"
)
# Install configuration files
install(FILES config/VisionDemo.conf
DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/VisionDemo
)
# Generate and install version file
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/VisionDemoConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/VisionDemoConfigVersion.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/VisionDemo
)
Installation Path Descriptions
Standard paths provided by GNUInstallDirs:
| Variable | Default Value | Description |
|---|---|---|
<span>CMAKE_INSTALL_BINDIR</span> |
<span>bin</span> |
Executable files |
<span>CMAKE_INSTALL_LIBDIR</span> |
<span>lib</span> |
Library files |
<span>CMAKE_INSTALL_INCLUDEDIR</span> |
<span>include</span> |
Header files |
<span>CMAKE_INSTALL_SYSCONFDIR</span> |
<span>etc</span> |
Configuration files |
<span>CMAKE_INSTALL_DATADIR</span> |
<span>share</span> |
Data files |
Using Installation
# Configure and build
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
# Install to specified directory
cmake --install build --prefix /opt/VisionDemo
# Or use traditional method
cd build && make install
Custom Build Targets
Version Information Target
add_custom_target(version_info
COMMAND ${CMAKE_COMMAND} -E echo "Building ${PROJECT_NAME} version ${PROJECT_VERSION}"
COMMAND ${CMAKE_COMMAND} -E echo "C++ compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}"
COMMAND ${CMAKE_COMMAND} -E echo "Build type: ${CMAKE_BUILD_TYPE}"
COMMAND ${CMAKE_COMMAND} -E echo "Install prefix: ${CMAKE_INSTALL_PREFIX}"
VERBATIM
)
# Make the main target depend on version information
add_dependencies(VisionDemo version_info)
Code Formatting Target
find_program(CLANG_FORMAT clang-format)
if(CLANG_FORMAT)
file(GLOB_RECURSE SOURCE_FILES
"${CMAKE_SOURCE_DIR}/src/*.cpp"
"${CMAKE_SOURCE_DIR}/src/*.h"
"${CMAKE_SOURCE_DIR}/include/*.h"
)
add_custom_target(format
COMMAND ${CLANG_FORMAT} -i ${SOURCE_FILES}
COMMENT "Formatting source code with clang-format"
VERBATIM
)
endif()
Documentation Generation Target
find_package(Doxygen)
if(DOXYGEN_FOUND)
set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile.in)
set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)
add_custom_target(docs
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Generating API documentation with Doxygen"
VERBATIM
)
endif()
Complete Example Project
Project Structure
VisionDemo/
βββ CMakeLists.txt
βββ cmake/
β βββ Dependencies.cmake
β βββ CompilerOptions.cmake
βββ include/
β βββ VisionDemo/
β βββ VisionDemo.h
β βββ Config.h
βββ src/
β βββ main.cpp
β βββ VisionDemo.cpp
β βββ Config.cpp
βββ tests/
β βββ test_VisionDemo.cpp
βββ docs/
β βββ Doxyfile.in
βββ README.md
Main CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(VisionDemo
VERSION 1.2.0
DESCRIPTION "Modern VisionDemo Management System"
LANGUAGES CXX
)
# --- C++ Standard Settings ---
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# --- Output Directory Settings ---
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# --- Environment Detection ---
if(DEFINED ENV{CONDA_PREFIX})
set(CMAKE_PREFIX_PATH $ENV{CONDA_PREFIX})
message(STATUS "Using Conda environment: $ENV{CONDA_PREFIX}")
endif()
# --- Include Submodules ---
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
include(Dependencies)
include(CompilerOptions)
# --- Include Directories ---
include_directories(${PROJECT_SOURCE_DIR}/include)
# --- Source File Collection ---
file(GLOB_RECURSE SOURCES src/*.cpp)
file(GLOB_RECURSE HEADERS include/*.h)
# --- Create Library Target ---
add_library(VisionDemoLib STATIC ${SOURCES} ${HEADERS})
target_include_directories(VisionDemoLib PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
# --- Create Executable Target ---
add_executable(VisionDemo src/main.cpp)
target_link_libraries(VisionDemo PRIVATE
VisionDemoLib
fmt::fmt
spdlog::spdlog
)
# --- Set Compiler Options ---
setup_compiler_options(VisionDemo)
setup_compiler_options(VisionDemoLib)
# --- Version Information ---
add_custom_target(version_info
COMMAND ${CMAKE_COMMAND} -E echo "Building ${PROJECT_NAME} ${PROJECT_VERSION}"
COMMAND ${CMAKE_COMMAND} -E echo "Compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}"
)
add_dependencies(VisionDemo version_info)
# --- Tests ---
option(BUILD_TESTS "Build tests" ON)
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
# --- Installation Configuration ---
include(GNUInstallDirs)
install(TARGETS VisionDemo VisionDemoLib
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(DIRECTORY include/VisionDemo
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FILES_MATCHING PATTERN "*.h"
)
# --- Package Configuration ---
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/VisionDemoConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/VisionDemoConfigVersion.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/VisionDemo
)
cmake/Dependencies.cmake
include(FetchContent)
# fmt library
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 9.1.0
)
# spdlog library
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.11.0
)
# If building tests, add Google Test
option(BUILD_TESTS "Build tests" ON)
if(BUILD_TESTS)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.12.1
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
endif()
# Make all dependencies available
FetchContent_MakeAvailable(fmt spdlog)
if(BUILD_TESTS)
FetchContent_MakeAvailable(googletest)
endif()
cmake/CompilerOptions.cmake
function(setup_compiler_options target)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(${target} PRIVATE
-Wall
-Wextra
-Wpedantic
-Wno-unused-parameter
-Wno-unused-variable
$<$<CONFIG:Debug>:-g3 -O0>
$<$<CONFIG:Release>:-O3 -DNDEBUG>
)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_compile_options(${target} PRIVATE
/W4
/utf-8
$<$<CONFIG:Debug>:/Od /RTC1>
$<$<CONFIG:Release>:/O2 /DNDEBUG>
)
endif()
endfunction()
Building and Using
# Clone the project
git clone <repository_url>
cd VisionDemo
# Configure the build
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
# Build
cmake --build build --parallel
# Run tests
cd build && ctest --verbose
# Install
cmake --install build --prefix install
# View version information
cd build && make version_info
Best Practices Summary
β Recommended Practices
- Use modern CMake syntax (3.14+)
- Explicitly specify target properties instead of global variables
- Use
<span>target_*</span>series commands instead of deprecated global commands - Use link visibility appropriately (PRIVATE/PUBLIC/INTERFACE)
- Organize configuration files (cmake/ directory)
- Provide complete installation configuration
- Support multi-platform compilers
β Practices to Avoid
- Do not use outdated CMake syntax
- Avoid hardcoding paths
- Do not ignore compiler differences
- Avoid using
<span>target_*</span>commands before<span>add_executable</span> - Do not mix global and target-specific settings
π§ Debugging Tips
# Print variable values
message(STATUS "CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}")
message(STATUS "PROJECT_VERSION: ${PROJECT_VERSION}")
# Print all variables
get_cmake_property(_variableNames VARIABLES)
foreach(_variableName ${_variableNames})
message(STATUS "${_variableName}=${${_variableName}}")
endforeach()
# Detailed build output
set(CMAKE_VERBOSE_MAKEFILE ON)
This guide covers all important aspects of modern CMake project configuration, from basic setup to advanced features, and can serve as a reference template for actual project development.