CMake是一款强大的跨平台构建工具,通过编写一份统一的 CMakeLists.txt 配置文件,即可为不同平台(如 Windows、Linux、macOS)生成相应的构建系统(如 Visual Studio 的 .sln 文件或 Makefile),随后再调用平台原生的编译器进行编译。它极大地简化了 C/C++ 项目的构建管理流程,是许多大型开源项目的首选。
理解构建系统与生成器
构建系统指的是平台原生的项目工程文件,例如:
- Windows:Visual Studio 的
.sln 和 .vcxproj 文件
- Linux/Unix:
Makefile 文件
CMake 的作用就是根据 CMakeLists.txt 生成这些构建系统文件,它支持多种生成器(Generator)。可以通过以下命令查看 CMake 支持生成哪些构建系统:
cmake -G
CMake 构建流程详解
标准的构建流程分为两步:
-
生成构建系统:通过解析 CMakeLists.txt 文件,检查环境(如编译器),并生成对应的项目文件。
- 命令:
cmake [选项] <CMakeLists.txt所在路径>
- 工具:可使用
cmake 命令行或 cmake-gui 图形界面。
-
编译与安装:使用生成的原生构建工具(如 msbuild、nmake、make)进行编译和安装。
常用构建选项与命令
-G:指定生成器(Generator)。
-D:定义或覆盖 CMake 变量。
-S 和 -B:分别指定源代码目录和构建目录(现代 CMake 推荐用法)。
一个典型的跨平台构建步骤如下:
# 1. 在项目根目录创建并进入构建目录
mkdir build && cd build
# 2. 生成构建系统(根据平台选择生成器)
# 对于 Visual Studio 2015
cmake -G "Visual Studio 14 2015" ..
# 对于 Windows NMake
cmake -G "NMake Makefiles" ..
# 对于 Linux/Unix
cmake -G "Unix Makefiles" ..
# 3. 编译项目
cmake --build .
# 或者使用原生工具:make / msbuild / ninja 等
# 4. 安装(如果需要)
cmake --install .
更简洁的现代命令方式:
# 在项目根目录执行,-S 指定源码目录,-B 指定构建目录
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
cmake --install build
CMake 核心概念与语法
关键文件类型
- 目录文件 (
CMakeLists.txt):每个目录的构建入口。顶层 CMakeLists.txt 通过 add_subdirectory() 引入子目录。
- 脚本文件 (
.cmake):通常包含可复用的宏(macro)或函数(function)。可通过 cmake -P script.cmake 直接执行测试,或通过 include() 命令在 CMakeLists.txt 中加载。
- 模块文件 (
Find<PackageName>.cmake):由 find_package() 命令调用,用于查找第三方依赖库(如 OpenCV),并设置相关变量(如 OpenCV_INCLUDE_DIRS, OpenCV_LIBRARIES)。
变量系统
CMake 中所有变量本质都是字符串,作用域分为:
- 函数作用域:在
function()...endfunction() 内定义的变量。
- 目录作用域:在
function 外定义的变量,对当前目录及其子目录可见。
- 缓存变量:使用
set(... CACHE ...) 设置,值在多次构建间持久化。
变量引用格式为 ${变量名},查找顺序为:函数作用域 -> 目录作用域 -> 缓存。
常用内置变量
| 变量名 |
含义与用途 |
PROJECT_SOURCE_DIR |
工程源代码根目录,即顶层 CMakeLists.txt 所在路径。 |
PROJECT_BINARY_DIR |
工程构建根目录,即执行 cmake 命令的路径(如 ./build)。 |
CMAKE_CURRENT_SOURCE_DIR |
当前处理的 CMakeLists.txt 所在目录。 |
CMAKE_CURRENT_BINARY_DIR |
对应于当前源目录的构建目录。 |
CMAKE_RUNTIME_OUTPUT_DIRECTORY |
指定可执行文件(.exe)的输出目录。 |
CMAKE_LIBRARY_OUTPUT_DIRECTORY |
指定库文件(.dll/.so/.lib/.a)的输出目录。 |
平台检测与条件控制
CMake 提供了平台检测变量,便于编写跨平台脚本:
if(WIN32)
message(STATUS "Building on Windows")
elseif(UNIX AND NOT APPLE)
message(STATUS "Building on Linux/Unix")
elseif(APPLE)
message(STATUS "Building on macOS")
endif()
条件判断支持丰富的关键字,如 AND、OR、NOT、EXISTS(检查文件)、DEFINED(检查变量)、MATCHES(正则匹配)以及字符串、数字、版本号比较等。
循环、函数与宏
CMake 支持 while() 和多种 foreach() 循环,以及 break()、continue() 控制。
宏 (macro) 和函数 (function) 用于封装可重用指令集,定义后可以像内置命令一样调用。两者主要区别在于作用域:函数内部有独立的作用域,而宏内部的操作直接影响到调用者的作用域。
定义构建目标
创建可执行文件与库
# 生成可执行文件
add_executable(MyApp main.cpp utils.cpp)
# 生成静态库
add_library(MyLib STATIC lib.cpp)
# 生成动态库/共享库
add_library(MyLib SHARED lib.cpp)
管理源文件列表
可以通过多种方式指定源文件:
- 直接在
add_executable/add_library 中列出。
- 使用
set() 命令将文件列表存入变量。
- 使用
file(GLOB ...) 命令通配匹配文件(需谨慎使用,可能导致新文件未被自动识别)。
# 示例:使用变量和 file(GLOB) 管理文件
set(APP_HEADERS include/utils.h)
set(APP_SOURCES src/main.cpp src/utils.cpp)
file(GLOB APP_UIS "ui/*.ui")
add_executable(MyApp ${APP_HEADERS} ${APP_SOURCES} ${APP_UIS})
file(GLOB_RECURSE) 可以递归匹配子目录中的文件。在复杂的运维与 DevOps 流水线中,清晰的文件管理是保证构建可靠性的基础。

(图示:CMake 项目中的典型文件结构示意)
目标属性设置(现代 CMake 风格)
现代 CMake 强调将属性(如头文件路径、编译选项、链接库)关联到具体的目标上。
# 为目标指定头文件搜索目录(PRIVATE表示仅本目标使用)
target_include_directories(MyApp PRIVATE include)
# 为目标添加编译选项
target_compile_options(MyApp PRIVATE -Wall -Wextra)
# 为目标链接其他库
target_link_libraries(MyApp PRIVATE MyLib)
# 为目标添加预处理宏定义
target_compile_definitions(MyApp PRIVATE VERSION=\"1.0\")
安装与打包
安装规则
使用 install() 命令定义安装规则,DESTINATION 路径通常相对于 CMAKE_INSTALL_PREFIX。
# 安装可执行文件到 bin 目录
install(TARGETS MyApp DESTINATION bin)
# 安装库文件到 lib 目录
install(TARGETS MyLib DESTINATION lib)
# 安装头文件到 include 目录
install(FILES include/utils.h DESTINATION include)
使用 CPack 打包
CPack 是 CMake 内置的打包工具,可以生成多种格式的安装包(如 .tar.gz、.zip、.sh、.deb、.rpm)。
# 在顶层 CMakeLists.txt 中设置 CPack 变量并引入模块
set(CPACK_GENERATOR "TGZ") # 设置生成器为 .tar.gz
set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
set(CPACK_PACKAGE_VERSION "1.0.0")
include(CPack)
打包命令:
# 在构建目录中
cmake --build . --target package # 生成二进制包
cmake --build . --target package_source # 生成源码包
# 或直接使用 cpack 命令
cpack -G TGZ
对于 Linux 平台,STGZ(自解压 .sh 安装包)格式很常用。其安装脚本可以通过 CPACK_STGZ_HEADER_FILE 变量进行自定义,实现更复杂的安装逻辑,这在自动化部署脚本中尤为有用,可以视为一种特定的 Shell 脚本实践。
常用命令速览
字符串操作
# 查找与替换
string(FIND "hello world" "world" pos) # pos = 6
string(REPLACE "world" "cmake" result "hello world") # result = "hello cmake"
# 拼接
string(APPEND str "Hello, ") # str = "Hello, "
string(JOIN ";" list_str a b c) # list_str = "a;b;c"
# 大小写与裁剪
string(TOLOWER "CMake" lower_str) # lower_str = "cmake"
string(STRIP " text " stripped) # stripped = "text"
消息打印
message() 命令用于输出信息,支持不同级别:
message(STATUS "这是状态信息") # 最常见,用于提示进度
message(WARNING "这是一个警告")
message(SEND_ERROR "这是一个错误,但会继续")
message(FATAL_ERROR "致命错误,将停止构建")
文件与目录操作
# 复制文件或目录
file(COPY data/ DESTINATION ${CMAKE_BINARY_DIR}/bin)
# 创建目录
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/generated)
掌握 CMake 的这些核心概念和命令,尤其是变量作用域、条件判断和现代的目标属性设置方法,是编写高效、可维护的构建脚本的关键。这与编写高质量的 Shell 脚本有着异曲同工之妙,都需要对执行环境和逻辑流有清晰的控制。