现在位置: 首页 > CMake 教程 > 正文

CMake 实战练习

本文将演示如何使用 CMake 管理一个中等复杂度的 C++ 项目,从创建项目到编译和运行的完整流程。

内容涵盖基本配置、库链接、单元测试、自定义命令以及跨平台交叉编译等高级特性。


CMake 构建流程

下图展示了 CMake 从源码到最终可执行文件的完整构建流程:

CMake 构建流程


项目概述

我们将构建一个包含主程序、静态库和单元测试的 C++ 项目。

库中提供数学运算功能(加法、减法、乘法),主程序调用这些功能,测试代码验证其正确性。

项目目录结构

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 Build(源外构建)是 CMake 的最佳实践:所有构建产物(Makefile、目标文件、可执行文件)都放在独立的 build 目录中,不会污染源码目录。


创建 CMakeLists.txt 文件

CMakeLists.txt 是 CMake 的核心配置文件,采用层级结构,根目录和每个子目录各有一个。

根目录 CMakeLists.txt

根配置定义项目级信息,并通过 add_subdirectory 管理子目录。

实例

cmake_minimum_required(VERSION 3.10)    # 指定最低 CMake 版本要求
project(MyProject VERSION 1.0)          # 定义项目名称和版本号

# 设置 C++ 标准为 C++11,并设为强制要求
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 添加全局头文件搜索路径(PROJECT_SOURCE_DIR 为项目根目录)
include_directories(${PROJECT_SOURCE_DIR}/src/include)

# 添加子目录,CMake 将依次处理其中的 CMakeLists.txt
add_subdirectory(src)      # 处理库和可执行文件
add_subdirectory(tests)    # 处理测试

src 目录 CMakeLists.txt

此文件定义库目标(MyLib)和可执行文件目标(MyExecutable),并建立依赖关系。

实例

# 创建静态库目标 MyLib,由两个源文件组成
add_library(MyLib STATIC
    lib/module1.cpp
    lib/module2.cpp
)

# 为 MyLib 指定公开头文件路径(PUBLIC 表示使用者也会继承此路径)
target_include_directories(MyLib PUBLIC ${CMAKE_SOURCE_DIR}/src/include)

# 创建可执行文件目标 MyExecutable
add_executable(MyExecutable main.cpp)

# 将 MyLib 链接到可执行文件(PRIVATE 表示仅 MyExecutable 需要)
target_link_libraries(MyExecutable PRIVATE MyLib)

PRIVATE 和 PUBLIC 的区别:PRIVATE 表示依赖仅当前目标需要,PUBLIC 表示依赖会传递给链接了当前目标的其他目标。

tests 目录 CMakeLists.txt

测试配置使用 Google Test 框架,需要先查找并引入 GTest 包。

实例

# 在 src/CMakeLists.txt 中添加以下内容

# 在 MyExecutable 构建完成后打印提示信息
add_custom_command(
    TARGET MyExecutable
    POST_BUILD                          # 构建完成后执行
    COMMAND ${CMAKE_COMMAND} -E echo "MyExecutable 构建完成!"
    COMMENT "打印构建完成信息"            # 构建日志中显示的说明
)

自定义目标(add_custom_target)

自定义目标是一个独立的目标,不产生输出文件,用于执行特定任务。

实例

# 在 src/CMakeLists.txt 中添加以下内容

# 创建 run 目标,执行编译后的程序
add_custom_target(run
    COMMAND ${CMAKE_BINARY_DIR}/src/MyExecutable
    DEPENDS MyExecutable                # 依赖 MyExecutable,确保先编译
    COMMENT "运行 MyExecutable"
)

运行自定义目标:

# 在 build 目录下执行
make run
# 或
cmake --build . --target run

自定义命令和自定义目标的区别:add_custom_command 附着在某个目标上,在目标构建的特定阶段触发;add_custom_target 是一个独立的目标,需要显式指定才会执行。


跨平台和交叉编译

CMake 的核心优势之一就是跨平台支持,可以轻松切换目标平台和架构。

指定目标平台

通过 CMAKE_SYSTEM_NAME 变量可以指定目标平台。

# 为 Linux 平台交叉编译
cmake -DCMAKE_SYSTEM_NAME=Linux ..

# 为 Windows 平台交叉编译
cmake -DCMAKE_SYSTEM_NAME=Windows ..

使用工具链文件

对于复杂的交叉编译场景,推荐使用工具链文件集中管理所有平台相关配置。

首先创建工具链文件 toolchain.cmake:

实例

# 文件路径:MyProject/toolchain.cmake
# ARM Linux 交叉编译工具链配置

# 指定目标系统
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

# 指定交叉编译器路径(根据实际环境调整)
set(CMAKE_C_COMPILER   arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)

# 指定目标系统的根文件系统路径(可选,用于查找库和头文件)
set(CMAKE_FIND_ROOT_PATH /path/to/arm/sysroot)

# 只在目标 sysroot 中查找程序(不在主机系统中查找)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# 只在目标 sysroot 中查找库文件
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
# 只在目标 sysroot 中查找头文件
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

使用工具链文件进行构建:

# 在 build 目录下执行
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake ..
cmake --build .

工具链文件将平台相关的编译器、链接器、库路径等配置集中管理,使得切换目标平台只需指定不同的工具链文件,无需修改 CMakeLists.txt。


构建类型与常用选项

CMake 支持多种构建类型,通过 CMAKE_BUILD_TYPE 变量控制。

构建类型 编译选项 适用场景
Debug -g(调试符号) 开发调试阶段,需要断点和变量查看
Release -O2 -DNDEBUG(优化 + 去除断言) 生产发布,追求运行速度
RelWithDebInfo -O2 -g(优化 + 调试符号) 需要性能分析或调试优化的发布版本
MinSizeRel -Os(体积优化) 嵌入式或对体积敏感的场景

指定构建类型:

# Debug 构建
cmake -DCMAKE_BUILD_TYPE=Debug ..

# Release 构建
cmake -DCMAKE_BUILD_TYPE=Release ..