前面我们已经基本学习了CMake的使用,我们知道CMake是一套特别强大的编译构建工具链。那么它可以用于哪些工程场景呢?本期就来介绍一下CMake在编译可执行二进制程序、链接动静态库、打包分发等各种场景下的应用。
相关代码已经上传至作者的个人gitee:CMake 学习: CMake工具开发介绍仓库,虽然标记的是C++喜欢请点个赞谢谢
目录
代码示例
源码
cpp
#include<iostream>
int main()
{
std::cout<<"hello world"<<std::endl;
return 0;
}
CMakeLists.txt
bash
#设置CMake最低版本
cmake_minimum_required(VERSION 3.18)
#项目名称
project(hello VERSION 1.0.0)
#添加构建目标
add_executable(main main.cpp)
#安装路径设置
include(GNUInstallDirs)
#安装本地
install(TARGETS main DESTINATION ${CMAKE_INSTALL_BINDIR})
# printf
message(STATUS "PROJECT_NAME: ${PROJECT_NAME}")
# print version
message(STATUS "PROJECT_VERSION:" ${PROJECT_VERSION})
message(STATUS "PROJECT_VERSION_MAJOR:" ${PROJECT_VERSION_MAJOR})
message(STATUS "PROJECT_VERSION_MINOR:" ${PROJECT_VERSION_MINOR})
message(STATUS "PROJECT_VERSION_PATCH:" ${PROJECT_VERSION_PATCH})
编译链接运行,结果为:

成功下载。

下面我们针对以上的示例,对重点命令进行解释
重点命令
cmake_minimum_required
作用 :版本兼容性保证 :确保构建系统使用指定版本以上的 CMake,避免使用过时的命令和特性。策略设置 :根据指定版本自动设置 CMake 策略(policies)的行为,平衡新功能与向后兼容性。错误预防 :在项目配置阶段早期检测版本不匹配,防止后续构建失败。通常应该在CMake文件的第一行
表达式:
bash
cmake_minimum_required(VERSION <min>[.<patch>] [...<max>[.<patch>]] [FATAL_ERROR])
参数:
1. VERSION(必需)
-
类型:关键字,标识版本参数的开始
-
作用:告诉 CMake 后面的参数是版本号
2. <min>(必需)
-
格式 :
major.minor[.patch[.tweak]] -
示例:
-
3.18→ 3.18.0 -
3.18.2→ 3.18.2
-
-
作用:最低要求的 CMake 版本
-
规则:必须是数值,至少包含 major.minor 两部分
3. ...<max>(可选,CMake 3.12+)
-
格式 :
...major.minor[.patch] -
示例 :
3.18...3.25 -
作用:指定可接受的 CMake 版本范围
-
适用场景:
-
项目使用了特定版本引入的功能
-
避免使用更高版本中可能不兼容的更改
-
4. FATAL_ERROR(可选,已弃用)
-
历史:早期版本中用于强调版本不匹配是致命错误
-
现状:现在版本不匹配总是致命错误,已不再需要此参数
现代新项目推荐CMake3.18版本及以上,因为文档丰富、社区支持力度高。可以用以下指令查询CMake版本
bash
cmake -version
project
作用:
-
项目标识:定义项目的基本信息(名称、版本、描述等)
-
初始化构建环境:设置编译器、语言标准、系统检测等
-
变量定义:创建一系列项目相关的CMake变量
-
语言启用:指定项目使用的编程语言,并启用相应的编译器和特性
表达式:
bash
#完整版本
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[HOMEPAGE_URL <url-string>]
[LANGUAGES <language-name>...])
#现代写法
project(<PROJECT-NAME>
VERSION <version>
DESCRIPTION <description>
LANGUAGES <languages>)
参数:
1. <PROJECT-NAME>(必需)
-
类型:字符串
-
规则:只能包含字母、数字、下划线、连字符
-
作用:
-
定义项目标识符
-
设置
PROJECT_NAME变量 -
设置
CMAKE_PROJECT_NAME变量
-
2. VERSION(可选,推荐使用)
-
格式:数字序列,用点分隔
-
示例:
bashVERSION 1.0.0 VERSION 2.3.1 VERSION 2024.12.31.1 -
生成的变量:
bashPROJECT_VERSION # 完整版本 "1.2.3" PROJECT_VERSION_MAJOR # 主版本 "1" PROJECT_VERSION_MINOR # 次版本 "2" PROJECT_VERSION_PATCH # 修订号 "3" PROJECT_VERSION_TWEAK # 构建号 "4"(如果有)
3. DESCRIPTION(可选,CMake 3.9+)
-
类型:字符串
-
作用:项目简短描述
-
生成变量 :
PROJECT_DESCRIPTION
4. HOMEPAGE_URL(可选,CMake 3.12+)
-
类型:URL字符串
-
作用:项目主页链接
-
生成变量 :
PROJECT_HOMEPAGE_URL
5. LANGUAGES(可选,默认"C"和"CXX")
-
取值 :
C、CXX、CUDA、OBJC、OBJCXX、Fortran、ASM、NONE -
作用:
-
启用指定语言的编译器检测
-
设置语言相关的默认变量
-
影响构建系统配置
-
project指令执行后,会生成以下新变量:
| 变量名 | 作用 | 生成条件 | 应用场景 |
|---|---|---|---|
PROJECT_NAME |
当前项目的名称 | 总是生成 | 1. 在CMake脚本中引用当前项目 2. 作为子目录项目标识 3. 生成文件名前缀 |
PROJECT_VERSION |
完整的项目版本号 | 使用 VERSION 参数 |
1. 在代码中嵌入版本信息 2. 生成版本配置文件 3. 打包和发布时使用 |
PROJECT_VERSION_MAJOR |
主版本号 | 使用 VERSION 参数 |
1. API 兼容性检查 2. 主要特性变更标识 |
PROJECT_VERSION_MINOR |
次版本号 | 使用 VERSION 参数 |
1. 新功能添加标识 2. 向后兼容的更新 |
PROJECT_VERSION_PATCH |
修订号/补丁号 | 使用 VERSION 参数且版本至少有3部分 |
1. Bug修复标识 2. 微小的改进 |
PROJECT_VERSION_TWEAK |
构建号/微调号 | 使用 VERSION 参数且版本有4部分 |
1. 构建序号 2. 内部测试版本 |
PROJECT_DESCRIPTION |
项目描述文本 | 使用 DESCRIPTION 参数 |
1. 生成文档说明 2. 安装包描述信息 |
PROJECT_HOMEPAGE_URL |
项目主页URL | 使用 HOMEPAGE_URL 参数 |
1. 生成项目链接 2. 安装包元数据 |
PROJECT_SOURCE_DIR |
当前项目源码目录 | 总是生成 | 1. 引用项目源码文件 2. 配置文件的相对路径 3. 资源文件定位 |
PROJECT_BINARY_DIR |
当前项目构建目录 | 总是生成 | 1. 输出构建产物路径 2. 临时文件存放位置 3. 生成的文件存放 |
CMAKE_PROJECT_NAME |
顶级项目名称(第一个project) | 总是生成 | 1. 在多项目构建中识别根项目 2. 全局配置引用 |
CMAKE_PROJECT_VERSION |
顶级项目版本 | 第一个project使用 VERSION 参数 |
1. 整体版本管理 2. 依赖关系检查 |
<PROJECT-NAME>_SOURCE_DIR |
项目源码目录(大写名称) | 总是生成 | 1. 兼容旧代码 2. 明确指定特定项目目录 |
<PROJECT-NAME>_BINARY_DIR |
项目构建目录(大写名称) | 总是生成 | 1. 兼容旧代码 2. 跨项目引用构建产物 |
<PROJECT-NAME>_VERSION |
项目版本(大写名称) | 使用 VERSION 参数 |
1. 兼容旧代码 2. 脚本中明确指定项目版本 |
变量使用示例表
| 变量 | 示例代码 | 解释 |
|---|---|---|
PROJECT_NAME |
message("Building ${PROJECT_NAME}") |
输出项目名称 |
PROJECT_VERSION |
configure_file(version.h.in version.h) |
生成版本头文件 |
PROJECT_SOURCE_DIR |
include_directories(${PROJECT_SOURCE_DIR}/include) |
添加项目包含目录 |
PROJECT_BINARY_DIR |
set(OUTPUT_DIR ${PROJECT_BINARY_DIR}/bin) |
设置输出目录 |
| 组合使用 | set(TARGET_NAME "${PROJECT_NAME}-v${PROJECT_VERSION}") |
生成带版本的目标名 |
特殊场景变量表
| 场景 | 关键变量 | 用途 |
|---|---|---|
| 多项目构建 | CMAKE_PROJECT_NAME |
识别根项目 |
| 子目录项目 | PROJECT_SOURCE_DIR |
每个子项目有自己的源码目录 |
| 版本化安装 | PROJECT_VERSION |
生成版本化的安装路径 |
| 条件编译 | PROJECT_VERSION_MAJOR |
根据主版本启用/禁用特性 |
| 资源管理 | PROJECT_SOURCE_DIR |
定位项目资源文件 |
| 测试输出 | PROJECT_BINARY_DIR |
测试结果输出路径 |
| 文档生成 | PROJECT_DESCRIPTION |
嵌入到文档中 |
| 打包部署 | 所有版本变量 | 生成包元数据 |
项目变量继承关系表
| 变量类型 | 作用域 | 示例 | 说明 |
|---|---|---|---|
| 项目局部变量 | 当前项目 | PROJECT_SOURCE_DIR |
每个项目独立 |
| 全局顶级变量 | 整个构建 | CMAKE_PROJECT_NAME |
第一个project设置 |
| 大写别名变量 | 兼容性 | MYPROJECT_SOURCE_DIR |
大写项目名前缀 |
| 语言相关变量 | 全局/项目 | CMAKE_CXX_STANDARD |
影响编译设置 |
我们来试试
bash
# CMake最低版本要求
cmake_minimum_required(VERSION 3.10)
# 设置项目名称
project(hello VERSION 1.2.3 LANGUAGES CXX)
# 设置C++标准
set(CMAKE_CXX_STANDARD 11)
# 添加可执行文件
add_executable(hello main.cpp)
# add_executable(app src/app.cpp)
# 安装可执行文件
include(GNUInstallDirs)
install(TARGETS hello)
# install(TARGETS hello RUNTIME DESTINATION bin)
# 输出一些调试信息
# 项目名称相关
message(STATUS "PROJECT_NAME: ${PROJECT_NAME}")
message(STATUS "CMAKE_PROJECT_NAME: ${CMAKE_PROJECT_NAME}")
message(STATUS "PROJECT_LANGUAGES: ${PROJECT_LANGUAGES}")
# 版本信息
message(STATUS "PROJECT_VERSION: ${PROJECT_VERSION}")
message(STATUS "PROJECT_VERSION_MAJOR: ${PROJECT_VERSION_MAJOR}")
message(STATUS "PROJECT_VERSION_MINOR: ${PROJECT_VERSION_MINOR}")
message(STATUS "PROJECT_VERSION_PATCH: ${PROJECT_VERSION_PATCH}")
# 目录信息
message(STATUS "CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}")
message(STATUS "CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR}")
message(STATUS "PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
message(STATUS "PROJECT_BINARY_DIR: ${PROJECT_BINARY_DIR}")
message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")
message(STATUS "CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}")
# 文件信息
message(STATUS "CMAKE_CURRENT_LIST_FILE: ${CMAKE_CURRENT_LIST_FILE}")
message(STATUS "CMAKE_CURRENT_LIST_DIR: ${CMAKE_CURRENT_LIST_DIR}")
# 安装路径信息
message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}")
message(STATUS "CMAKE_INSTALL_BINDIR: ${CMAKE_INSTALL_BINDIR}")
结果为:

include
作用:
-
模块化引入:将其他CMake文件的内容导入到当前位置执行
-
代码复用:复用预定义的CMake模块、函数和宏
-
配置扩展:引入项目特定的配置或第三方CMake脚本
-
功能集成:使用标准CMake模块提供的功能和变量
表达式:
bash
#完整形式
include(<file|module>
[RESULT_VARIABLE <var>]
[OPTIONAL]
[NO_POLICY_SCOPE]
)
#路径名形式
include(相对/绝对路径)
include(${变量}/文件名)
include(模块名) # 从CMAKE_MODULE_PATH搜索
参数:
1. <file|module>(必需)
-
类型:字符串,指定要包含的文件或模块名
-
形式:
-
相对路径:相对于当前文件所在目录
-
绝对路径:完整的文件路径
-
模块名 :CMake模块名称,会从
CMAKE_MODULE_PATH搜索
-
2. RESULT_VARIABLE <var>(可选)
-
作用:将包含操作的结果存储到指定变量中
-
变量值:
-
成功:包含文件的完整路径
-
失败:
NOTFOUND(未使用OPTIONAL时)或空值(使用OPTIONAL时)
-
3. OPTIONAL(可选)
-
作用:如果文件不存在,不报错,继续执行
-
使用场景:包含可选配置文件,不影响主要构建流程
4. NO_POLICY_SCOPE(可选)
-
作用:不创建新的策略作用域
-
使用场景:高级用法,控制策略(policies)的传播范围
核心目录变量的变化
| 变量名 | include() 执行前(调用文件) | include() 执行中(被包含文件) | include() 执行后(返回调用文件) | 说明 |
|---|---|---|---|---|
| CMAKE_CURRENT_LIST_FILE | 调用文件的路径 | 被包含文件的路径 | 调用文件的路径 | 总是指向当前正在处理的CMake文件 |
| CMAKE_CURRENT_LIST_DIR | 调用文件所在目录 | 被包含文件所在目录 | 调用文件所在目录 | 总是等于 CMAKE_CURRENT_LIST_FILE 的目录部分 |
| CMAKE_CURRENT_LIST_LINE | 调用文件中的行号 | 被包含文件中的行号 | 调用文件中下一行行号 | 逐行递增,跨文件时继续计数 |
| CMAKE_CURRENT_SOURCE_DIR | 调用文件所在目录 | 保持不变 | 保持不变 | 相对于 add_subdirectory(),include() 不改变此变量 |
| CMAKE_CURRENT_BINARY_DIR | 调用文件对应构建目录 | 保持不变 | 保持不变 | 相对于 add_subdirectory(),include() 不改变此变量 |
项目级变量的变化
| 变量名 | include() 执行前 | include() 执行中 | include() 执行后 | 说明 |
|---|---|---|---|---|
| PROJECT_SOURCE_DIR | 当前项目源码目录 | 保持不变 | 保持不变 | 由最近的 project() 调用设置 |
| PROJECT_BINARY_DIR | 当前项目构建目录 | 保持不变 | 保持不变 | 由最近的 project() 调用设置 |
| CMAKE_SOURCE_DIR | 顶级源码目录 | 保持不变 | 保持不变 | 整个CMake项目的根目录 |
| CMAKE_BINARY_DIR | 顶级构建目录 | 保持不变 | 保持不变 | 整个CMake项目的构建根目录 |
| CMAKE_MODULE_PATH | 当前模块搜索路径 | 可能改变 | 保持改变 | 如果在被包含文件中修改,变化会持续 |
作用域和变量传递
| 类别 | 行为 | 示例 |
|---|---|---|
| 变量作用域 | 同一作用域 | 被包含文件可以直接访问和修改调用文件的变量 |
| 函数定义 | 全局可用 | 在被包含文件中定义的函数在包含后仍然可用 |
| 宏定义 | 全局可用 | 在被包含文件中定义的宏在包含后仍然可用 |
| 缓存变量 | 全局可用 | set(var value CACHE) 定义的变量全局可见 |
| 环境变量 | 可能改变 | set(ENV{var} value) 可能影响后续执行 |

install
作用:
-
安装规则定义:指定项目构建产物如何安装到目标目录
-
打包准备:为生成安装包(deb、rpm、msi等)提供元数据
-
部署配置:定义文件在目标系统中的布局结构
-
组件管理:支持组件化安装,允许用户选择安装部分功能
表达式:
bash
install(
<TYPE> <args>... | # 安装目标、文件、目录等
[EXPORT <export-name>] | # 导出安装目标
[SCRIPT <file>] | # 安装脚本
[CODE <code>] | # 安装代码片段
[EXPORT_ANDROID_MK <file>] # Android.mk导出
[DESTINATION <dir>] # 目标目录
[PERMISSIONS <permissions>...] # 权限设置
[CONFIGURATIONS <configs>...] # 配置限定
[COMPONENT <component>] # 组件标识
[OPTIONAL] # 可选安装
[EXCLUDE_FROM_ALL] # 排除默认安装
[NAMELINK_COMPONENT <component>] # 符号链接组件
[NAMELINK_ONLY|NAMELINK_SKIP] # 符号链接控制
[...]
)
参数:
安装目标类型(TYPE)
1. TARGETS <targets> - 安装构建目标
bash
install(TARGETS <target>...
[[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|
PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE]
[DESTINATION <dir>]
[PERMISSIONS <permissions>...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[NAMELINK_COMPONENT <component>]
[OPTIONAL] [EXCLUDE_FROM_ALL]
[NAMELINK_ONLY|NAMELINK_SKIP]
] [...]
[INCLUDES DESTINATION [<dir> ...]]
)
目标分类:
-
ARCHIVE:静态库(.a/.lib)
-
LIBRARY:动态库(.so/.dylib/.dll)
-
RUNTIME:可执行文件(.exe/.out)
-
FRAMEWORK:macOS框架
-
BUNDLE:macOS应用包
-
PUBLIC_HEADER:公共头文件
-
PRIVATE_HEADER:私有头文件
-
RESOURCE:资源文件
2. FILES <files> - 安装普通文件
bash
install(FILES <files>...
[TYPE <type> | DESTINATION <dir>]
[PERMISSIONS <permissions>...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL] [EXCLUDE_FROM_ALL]
)
3. DIRECTORY <dirs> - 安装目录
bash
install(DIRECTORY <dirs>...
[TYPE <type> | DESTINATION <dir>]
[FILE_PERMISSIONS <permissions>...]
[DIRECTORY_PERMISSIONS <permissions>...]
[USE_SOURCE_PERMISSIONS] [OPTIONAL] [MESSAGE_NEVER]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[FILES_MATCHING]
[[PATTERN <pattern> | REGEX <regex>]
[EXCLUDE] [PERMISSIONS <permissions>...]] [...]
)
通用参数
1. DESTINATION <dir> - 目标目录
bash
# 相对路径:相对于CMAKE_INSTALL_PREFIX
install(TARGETS myapp DESTINATION bin) # -> /usr/local/bin
# 绝对路径
install(FILES header.h DESTINATION /opt/myapp/include)
# 使用GNU标准目录变量
include(GNUInstallDirs)
install(TARGETS mylib
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
2. PERMISSIONS - 文件权限
bash
# 权限选项:
# OWNER_READ, OWNER_WRITE, OWNER_EXECUTE
# GROUP_READ, GROUP_WRITE, GROUP_EXECUTE
# WORLD_READ, WORLD_WRITE, WORLD_EXECUTE
# SETUID, SETGID
install(FILES script.sh
DESTINATION bin
PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
)
3. CONFIGURATIONS - 配置限定
bash
# 仅Debug配置安装调试符号
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/debug.pdb
DESTINATION bin
CONFIGURATIONS Debug
)
# 仅Release配置安装文档
install(FILES README.md
DESTINATION share/doc
CONFIGURATIONS Release
)
4. COMPONENT <component> - 组件标识
bash
# 将安装内容分组到不同组件
install(TARGETS myapp
RUNTIME DESTINATION bin
COMPONENT runtime
)
install(FILES documentation.pdf
DESTINATION share/doc
COMPONENT docs
)
# 用户可以按组件安装
# cmake --install . --component runtime
5. EXPORT <export-name> - 导出安装目标
bash
# 创建导出的CMake包,供其他项目使用
install(TARGETS mylib
EXPORT MyLibTargets
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
install(EXPORT MyLibTargets
FILE MyLibConfig.cmake
NAMESPACE MyLib::
DESTINATION lib/cmake/MyLib
)
6. SCRIPT 和 CODE - 自定义安装脚本
bash
# 执行安装脚本
install(SCRIPT postinstall.cmake)
# 执行代码片段
install(CODE "
execute_process(
COMMAND ${CMAKE_COMMAND} -E create_symlink
myapp-${PROJECT_VERSION} myapp
WORKING_DIRECTORY \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin
)
")
特殊参数
OPTIONAL - 可选安装
bash
# 如果文件不存在,不报错
install(FILES optional.txt
DESTINATION etc
OPTIONAL
)
EXCLUDE_FROM_ALL - 排除默认安装
bash
# 不包含在默认安装中,需显式指定
install(TARGETS tests
RUNTIME DESTINATION bin
EXCLUDE_FROM_ALL
)
# 安装:cmake --install . --component tests
RENAME <name> - 重命名文件
bash
install(FILES config.txt
DESTINATION etc
RENAME myapp.conf
)
NAMELINK 相关参数 - 符号链接控制
bash
# 控制库版本符号链接
install(TARGETS mylib
LIBRARY
DESTINATION lib
NAMELINK_COMPONENT devel
PUBLIC_HEADER
DESTINATION include
COMPONENT devel
)
cmake --install默认下载路径为usr/local

add_executable
作用 :add_executable 告诉 CMake 需要从指定的源文件构建一个可执行文件。它创建一个目标(target),后续可以通过其他命令为该目标添加依赖、编译选项等。最终在生成构建系统时,CMake 会根据这个目标生成相应的构建规则。
表达式:
bash
add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL]
source1 [source2 ...])
参数:
-
<name>:目标的逻辑名称,也是生成的可执行文件的基本名称 (平台会自动添加后缀,如 Windows 加.exe)。该名称可用于其他命令中引用此目标。 -
WIN32:仅影响 Windows 平台。设置后,生成的程序入口点为WinMain(而不是main),且链接时指定/SUBSYSTEM:WINDOWS。 -
MACOSX_BUNDLE:仅影响 macOS。设置后,构建输出将是一个.app包,而非普通的可执行文件。 -
EXCLUDE_FROM_ALL:若指定,该目标不会包含在默认构建目标(如ALL)中,需要手动构建(例如make target_name)。 -
源文件列表 :可以是多个文件,支持使用变量、生成器表达式(如
$<TARGET_PROPERTY:...>)或通配符(但通常不建议用通配符,因为 CMake 无法自动检测新增文件)。若省略源文件,可以后续通过target_sources()添加。
本期关于CMake在普通的可执行文件角度上的工程应用就讲完了,下面我们还会深入学习其他方面的内容。喜欢请点个赞
封面图自取:
