CMake Practical Exercise
This article demonstrates how to use CMake to manage a moderately complex C++ project, covering the complete process from project creation to compilation and execution.
The content covers basic configuration, library linking, unit testing, custom commands, and advanced features such as cross-platform cross-compilation.
CMake Build Process
The following diagram shows the complete CMake build process from source code to the final executable:
Project Overview
We will build a C++ project that contains a main program, a static library, and unit tests.
The library provides mathematical operations (addition, subtraction, multiplication). The main program calls these functions, and the test code verifies their correctness.
Project Directory Structure
MyProject/
├── CMakeLists.txt # 根构建配置(项目定义、全局设置)
├── src/
│ ├── main.cpp # 主程序入口
│ ├── CMakeLists.txt # src 子目录构建配置(库 + 可执行文件)
│ ├── lib/
│ │ ├── module1.cpp # 数学模块一:加法、减法
│ │ └── module2.cpp # 数学模块二:乘法
│ └── include/
│ └── mylib.h # 公共头文件(函数声明)
└── tests/
├── test_main.cpp # 单元测试用例
└── CMakeLists.txt # 测试子目录构建配置
Out-of-Source BuildIt is CMake's best practice: all build artifacts (Makefiles, object files, executables) are placed in a separate build directory, keeping the source directory clean.
Creating the CMakeLists.txt File
CMakeLists.txt is the core configuration file of CMake. It uses a hierarchical structure, with one in the root directory and one in each subdirectory.
Root Directory CMakeLists.txt
The root configuration defines project-level information and manages subdirectories via add_subdirectory.
Example
project(MyProject VERSION 1.0) # Define the project name and version number
# Set the C++ standard to C++11 and make it mandatory
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Add a global header file search path (PROJECT_SOURCE_DIR is the project root directory)
include_directories(${PROJECT_SOURCE_DIR}/src/include)
# Add subdirectories; CMake will process their CMakeLists.txt files in order
add_subdirectory(src) # Process the library and executable
add_subdirectory(tests) # Process the tests
src Directory CMakeLists.txt
This file defines the library target (MyLib) and the executable target (MyExecutable), and establishes the dependency relationship.
Example
add_library(MyLib STATIC
lib/module1.cpp
lib/module2.cpp
)
# Specify the public header file path for MyLib (PUBLIC means that consumers will also inherit this path)
target_include_directories(MyLib PUBLIC ${CMAKE_SOURCE_DIR}/src/include)
# Create the executable target MyExecutable
add_executable(MyExecutable main.cpp)
# Link MyLib to the executable (PRIVATE means that only MyExecutable needs it)
target_link_libraries(MyExecutable PRIVATE MyLib)
The difference between PRIVATE and PUBLIC:PRIVATEIndicates that the dependency is required only by the current target,PUBLICIndicates that the dependency is propagated to other targets that link to the current target.
tests Directory CMakeLists.txt
The test configuration uses the Google Test framework; first, find and include the GTest package.
Example
# Print a message after MyExecutable is built
add_custom_command(
TARGET MyExecutable
POST_BUILD # Execute after the build
COMMAND ${CMAKE_COMMAND} -E echo "MyExecutable build complete!"
COMMENT "Print build completion message" # Comment shown in the build log
)
Custom Targets (add_custom_target)
A custom target is an independent target that does not produce output files and is used to perform specific tasks.
Example
# Create a run target to execute the compiled program
add_custom_target(run
COMMAND ${CMAKE_BINARY_DIR}/src/MyExecutable
DEPENDS MyExecutable # Depends on MyExecutable to ensure it is built first
COMMENT "Run MyExecutable"
)
Running a custom target:
# 在 build 目录下执行 make run # 或 cmake --build . --target run
Differences between custom commands and custom targets:add_custom_commandAttached to a target, triggered at a specific stage of the target's build;add_custom_targetIs an independent target, executed only when explicitly specified.
Cross-platform and Cross-compilation
One of CMake's core advantages is cross-platform support, allowing easy switching of target platforms and architectures.
Specifying the Target Platform
The target platform can be specified using the CMAKE_SYSTEM_NAME variable.
# 为 Linux 平台交叉编译 cmake -DCMAKE_SYSTEM_NAME=Linux .. # 为 Windows 平台交叉编译 cmake -DCMAKE_SYSTEM_NAME=Windows ..
Using Toolchain Files
For complex cross-compilation scenarios, it is recommended to use a toolchain file to centrally manage all platform-related configurations.
First, create the toolchain file toolchain.cmake:
Example
# ARM Linux cross-compilation toolchain configuration
# Specify the target system
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
# Specify the cross-compiler path (adjust according to the actual environment)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
# Specify the target system's root filesystem path (optional, used for finding libraries and header files)
set(CMAKE_FIND_ROOT_PATH /path/to/arm/sysroot)
# Only search for programs in the target sysroot (not in the host system)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# Only search for library files in the target sysroot
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
# Only search for header files in the target sysroot
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
Building with the toolchain file:
# 在 build 目录下执行 cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake .. cmake --build .
The toolchain file centrally manages platform-related configurations such as compilers, linkers, and library paths, so switching target platforms only requires specifying a different toolchain file without modifying CMakeLists.txt.
Build Types and Common Options
CMake supports multiple build types, controlled by the CMAKE_BUILD_TYPE variable.
| Build type | Compiler options | Applicable scenarios |
|---|---|---|
| Debug | -g (debug symbols) | Development and debugging phase, when breakpoints and variable inspection are needed |
| Release | -O2 -DNDEBUG (optimization + removing assertions) | Production release, pursuing runtime speed |
| RelWithDebInfo | -O2 -g (optimization + debug symbols) | Release version that requires performance analysis or optimized debugging |
| MinSizeRel | -Os (size optimization) | Embedded systems or scenarios sensitive to size |
Specifying the build type:
# Debug 构建 cmake -DCMAKE_BUILD_TYPE=Debug .. # Release 构建 cmake -DCMAKE_BUILD_TYPE=Release ..Other extensions