Complete Guide to Modern CMake Configuration

πŸ“Œ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 
    $&lt;BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include&gt;
    $&lt;INSTALL_INTERFACE:include&gt;
)
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 &amp;&amp; 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 
    $&lt;BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include&gt;
    $&lt;INSTALL_INTERFACE:include&gt;
)

# --- 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
            $&lt;$&lt;CONFIG:Debug&gt;:-g3 -O0&gt;
            $&lt;$&lt;CONFIG:Release&gt;:-O3 -DNDEBUG&gt;
        )
    elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
        target_compile_options(${target} PRIVATE
            /W4
            /utf-8
            $&lt;$&lt;CONFIG:Debug&gt;:/Od /RTC1&gt;
            $&lt;$&lt;CONFIG:Release&gt;:/O2 /DNDEBUG&gt;
        )
    endif()
endfunction()

Building and Using

# Clone the project
git clone &lt;repository_url&gt;
cd VisionDemo

# Configure the build
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release

# Build
cmake --build build --parallel

# Run tests
cd build &amp;&amp; ctest --verbose

# Install
cmake --install build --prefix install

# View version information
cd build &amp;&amp; make version_info

Best Practices Summary

βœ… Recommended Practices

  1. Use modern CMake syntax (3.14+)
  2. Explicitly specify target properties instead of global variables
  3. Use <span>target_*</span> series commands instead of deprecated global commands
  4. Use link visibility appropriately (PRIVATE/PUBLIC/INTERFACE)
  5. Organize configuration files (cmake/ directory)
  6. Provide complete installation configuration
  7. Support multi-platform compilers

❌ Practices to Avoid

  1. Do not use outdated CMake syntax
  2. Avoid hardcoding paths
  3. Do not ignore compiler differences
  4. Avoid using <span>target_*</span> commands before <span>add_executable</span>
  5. 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.

Leave a Comment