本篇是 CMake 系列的收尾篇,在前两篇基础上深入语法细节、拆解真实工业级项目写法、提供可落地的工程规范与问题解决方案,最终形成从入门到企业级应用的完整知识体系。
1:CMake核心语法
前两篇用到了基础语法,本节系统梳理所有高频语法的进阶用法与底层规则,是写出复杂、可维护构建脚本的基础。
1:变量系统完全解析
1:变量的本质与列表
CMake 中所有变量本质都是字符串,所谓「列表」就是用分号;分隔的字符串:
set(SRC_LIST a.cpp b.cpp c.cpp) # 实际存储为:"a.cpp;b.cpp;c.cpp" # 取值 message(${SRC_LIST}) # 输出 a.cppb.cppc.cpp(分号被当成参数分隔) message("${SRC_LIST}") # 输出 a.cpp;b.cpp;c.cpp(加引号保留分号)- 未定义的变量取值为空字符串,不会报错,这是最常见的隐性 Bug 来源
- 取消定义:
unset(变量名) - 变量名严格区分大小写
2:三种变量作用域
| 作用域类型 | 定义方式 | 可见范围 | 向上传递方法 |
|---|---|---|---|
| 函数作用域 | 函数内set() | 仅当前函数内部 | set(变量 值 PARENT_SCOPE) |
| 目录作用域 | CMakeLists.txt 中set() | 当前目录及所有子目录 | 同上 |
| 缓存作用域 | set(变量 值 CACHE 类型 "描述") | 全局所有文件,跨配置保留 | 命令行-D变量=值覆盖 |
易错点:
- 子目录 / 函数内修改变量,父作用域默认看不到,必须加
PARENT_SCOPE - 缓存变量和普通变量同名时,普通变量会「遮蔽」缓存变量,导致逻辑混乱,避免同名
- 环境变量读取用
$ENV{变量名},设置用set(ENV{变量名} 值),仅当前 CMake 进程生效
3:缓存变量的完整用法
# 定义用户可配置的缓存变量 set(ENABLE_TEST OFF CACHE BOOL "是否启用单元测试") set(CMAKE_BUILD_TYPE Release CACHE STRING "构建类型") # 选项命令(等价于 BOOL 类型缓存变量) option(BUILD_SHARED_LIBS "是否构建动态库" OFF)- 缓存变量存储在
CMakeCache.txt中,多次配置保持不变 - 命令行通过
-D可以覆盖:cmake .. -DENABLE_TEST=ON - 用
cmake -LAH可以查看所有缓存变量及说明
2:流程控制进阶
1:if条件判断完整规则
真假判断常量:
- 真值:
1、ON、YES、TRUE、非零数字、非空且不是假值常量的字符串 - 假值:
0、OFF、NO、FALSE、NOTFOUND、空字符串、未定义变量
常用比较运算符:
| 比较类型 | 运算符 | 示例 |
|---|---|---|
| 数值比较 | EQUAL / LESS / GREATER / LESS_EQUAL / GREATER_EQUAL | if(VERSION GREATER 3) |
| 字符串比较 | STREQUAL / STRLESS / STRGREATER | if(COMPILER STREQUAL "GNU") |
| 版本比较 | VERSION_EQUAL / VERSION_LESS / VERSION_GREATER | if(CMAKE_VERSION VERSION_GREATER 3.20) |
| 逻辑运算 | AND / OR / NOT | if(A AND B) |
| 存在性判断 | DEFINED / TARGET / COMMAND / EXISTS | if(TARGET MyMath) |
| 路径判断 | IS_DIRECTORY / IS_ABSOLUTE / IS_SYMLINK | if(IS_DIRECTORY ${path}) |
2:循环结构
# 1. 遍历列表 foreach(file ${SRC_LIST}) message("处理文件: ${file}") endforeach() # 2. 遍历数字范围(0~10) foreach(i RANGE 10) message("i = ${i}") endforeach() # 3. 范围带步长(起始 结束 步长:1 10 2 → 1,3,5,7,9) foreach(i RANGE 1 10 2) message(${i}) endforeach() # 4. while 循环 set(cnt 0) while(cnt LESS 5) math(EXPR cnt "${cnt} + 1") if(cnt EQUAL 3) continue() # 跳过本次循环 endif() message("cnt = ${cnt}") endwhile()break()跳出循环,continue()跳过本次迭代return()直接终止当前函数 / 文件的执行
3:高频内置命令详解
1:list列表操作
set(LIST a b c d) list(APPEND LIST e f) # 追加元素 → a;b;c;d;e;f list(PREPEND LIST z) # 头部插入 → z;a;b;c;d;e;f list(REMOVE_ITEM LIST b) # 移除指定元素 → z;a;c;d;e;f list(REMOVE_AT LIST 0) # 按下标移除 → a;c;d;e;f list(LENGTH LIST len) # 求长度 → 5 list(FIND LIST "c" idx) # 查找元素下标 → 1 list(SORT LIST) # 排序 list(REVERSE LIST) # 反转 list(JOIN LIST " " str) # 用空格拼接成字符串 → "a c d e f" list(REMOVE_DUPLICATES LIST) # 去重2:string字符串操作
set(STR "Hello World") string(LENGTH ${STR} len) # 长度 → 11 string(TOUPPER ${STR} upper) # 转大写 → HELLO WORLD string(TOLOWER ${STR} lower) # 转小写 → hello world string(SUBSTRING ${STR} 0 5 sub) # 截取子串 → Hello string(REPLACE "World" "CMake" out ${STR}) # 替换 → Hello CMake string(FIND ${STR} "World" pos) # 查找位置 → 6 string(REGEX MATCH "W[a-z]+" match ${STR}) # 正则匹配 → World string(STRIP " hello " trimmed) # 去除首尾空格 → hello3:math数学运算
math(EXPR result "3 + 5 * 2") # 支持 + - * / % 等运算符 message(${result}) # 输出 134:file文件操作
# 读取文件内容 file(READ "file.txt" content) # 写入文件 file(WRITE "out.txt" "内容") file(APPEND "out.txt" "追加内容") # 通配符匹配文件 file(GLOB SRC "src/*.cpp") file(GLOB_RECURSE ALL_SRC "src/*.cpp") # 递归匹配 # 创建目录 file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/output) # 复制/删除文件 file(COPY "a.txt" DESTINATION ${CMAKE_BINARY_DIR}) file(REMOVE "tmp.txt") file(REMOVE_RECURSE "build/tmp") # 下载文件 file(DOWNLOAD "https://example.com/file.zip" "file.zip")再次提醒:
file(GLOB)仅在配置阶段执行一次,新增源文件必须重新执行 cmake 配置才能识别。
5:message消息级别
message(STATUS "普通状态信息") # 带 -- 前缀,最常用 message(WARNING "警告信息") # 黄色,不终止配置 message(AUTHOR_WARNING "开发者警告") message(FATAL_ERROR "致命错误") # 红色,直接终止配置 message(SEND_ERROR "错误") # 报错,继续执行但不生成构建文件 message(DEBUG "调试信息") # 需要开启 --log-level=DEBUG 才显示4:函数与宏
1:function函数
# 定义函数 function(calculate_sum a b result_var) math(EXPR res "${a} + ${b}") # 通过父作用域变量返回结果 set(${result_var} ${res} PARENT_SCOPE) endfunction() # 调用 calculate_sum(3 5 sum_result) message("3 + 5 = ${sum_result}") # 输出 8内置参数变量:
ARGC:参数总数ARGV:所有参数列表ARGV0/ARGV1...:按位置访问参数ARGN:超出形参声明的剩余参数(用于实现可变参数)
2:macro宏
macro(print_msg msg) message("宏输出: ${msg}") endmacro()3:function和marco的核心区别
| 特性 | function | macro |
|---|---|---|
| 作用域 | 创建独立局部作用域 | 继承调用者作用域,直接修改外部变量 |
| 参数 | 是真正的局部变量 | 是字符串替换,类似 C 宏 |
| return 行为 | 退出函数 | 退出调用者整个作用域 |
| 适用场景 | 有返回值、需要局部变量的逻辑 | 简单的代码片段替换、设置外部变量 |
最佳实践:优先用 function,逻辑更清晰,避免变量污染;只有需要直接修改调用者变量时才用 macro。
4:参数解析cmake_parse_arguments
用于实现带命名参数的复杂函数:
function(my_install) set(options OPTIONAL FAST) set(oneValueArgs TARGET DESTINATION) set(multiValueArgs FILES) cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) message("TARGET: ${ARG_TARGET}") message("DESTINATION: ${ARG_DESTINATION}") message("FILES: ${ARG_FILES}") if(ARG_FAST) message("启用快速模式") endif() endfunction() # 调用 my_install( TARGET myapp DESTINATION bin FILES a.txt b.txt FAST )5:生成器表达式详解
生成器表达式是 CMake 的高级特性,语法为$<...>,在生成阶段(配置结束后)才求值,可以获取配置阶段不知道的信息(如构建类型、目标路径、编译器 ID 等)。
常用生成器表达式
| 表达式 | 作用 | 示例 |
|---|---|---|
$<CONFIG:Debug> | 判断是否为 Debug 构建 | $<$<CONFIG:Debug>:-g> |
$<CXX_COMPILER_ID:GNU> | 判断是否为 GCC 编译器 | $<$<CXX_COMPILER_ID:GNU>:-Wall> |
$<PLATFORM_ID:Linux> | 判断是否为 Linux 平台 | $<$<PLATFORM_ID:Linux>:-pthread> |
$<TARGET_FILE:目标名> | 获取目标的完整输出路径 | $<TARGET_FILE:MyMath> |
$<TARGET_FILE_DIR:目标名> | 获取目标所在目录 | - |
$<INSTALL_INTERFACE:路径> | 安装时生效的路径 | 头文件安装路径 |
$<BUILD_INTERFACE:路径> | 构建时生效的路径 | 构建时头文件路径 |
$<IF:条件,值1,值2> | 三元表达式 | $<IF:$<CONFIG:Debug>,DEBUG,RELEASE> |
实战用法
# Debug 构建才加 -g 和 DEBUG 宏 target_compile_options(myapp PRIVATE $<$<CONFIG:Debug>:-g>) target_compile_definitions(myapp PRIVATE $<$<CONFIG:Debug>:DEBUG_MODE>) # GCC 才加 -Wall -Wextra target_compile_options(myapp PRIVATE $<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra>) # 同时设置构建时和安装时的头文件路径 target_include_directories(MyMath PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> )2:真实开源项目CMake实战
学习优秀开源项目的写法,是快速提升工程能力的最佳方式。本节拆解三个典型项目的 CMake 设计。
1:JsonCpp项目分析
JsonCpp 是经典的 JSON 解析库,它的 CMake 体现了成熟开源库的标准写法,非常适合作为库项目的模板。
核心配置亮点
1:标准的C++标准设置
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_EXTENSIONS OFF) # 关闭编译器扩展,保证跨编译器一致 set(CMAKE_CXX_STANDARD_REQUIRED ON) # 不支持该标准则直接报错最佳实践:永远关闭
CMAKE_CXX_EXTENSIONS,避免 GCC 的 GNU 扩展导致在 MSVC/Clang 上编译失败。
2:构建类型兜底
if(NOT DEFINED CMAKE_BUILD_TYPE AND NOT DEFINED CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE Release CACHE STRING "构建类型" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif()防止用户未指定构建类型,导致生成未优化的 Debug 版本。
3:编译器差异处理
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wconversion -Wshadow) elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") add_compile_options(-Wall -Wconversion -Wshadow -Wextra) elseif(MSVC) add_compile_options(/W4) endif()针对不同编译器设置合适的警告选项,体现跨平台兼容性。
4:同时构建静态库和动态库
通过选项控制,分别设置输出名、版本、安装规则,满足不同使用需求:
option(JSONCPP_WITH_STATIC "构建静态库" ON) option(JSONCPP_WITH_SHARED "构建动态库" ON) if(JSONCPP_WITH_STATIC) add_library(jsoncpp_static STATIC ${SRC}) set_target_properties(jsoncpp_static PROPERTIES OUTPUT_NAME jsoncpp) install(TARGETS jsoncpp_static EXPORT jsoncppTargets ...) endif() if(JSONCPP_WITH_SHARED) add_library(jsoncpp_shared SHARED ${SRC}) set_target_properties(jsoncpp_shared PROPERTIES OUTPUT_NAME jsoncpp VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) install(TARGETS jsoncpp_shared EXPORT jsoncppTargets ...) endif()5:完整的发布配置
- 安装头文件、库文件
- 生成 pkg-config 的
.pc文件 - 生成 Config.cmake 支持 find_package
- 提供 CTest 单元测试
- 配置 CPack 打包
2:libcurl项目分析
Curl 是大型跨平台网络库,它的 CMake 体现了复杂跨平台项目的工程能力。
设计亮点
1:丰富的功能选项
大量option()命令控制功能开关,用户可以按需裁剪:
option(CURL_USE_OPENSSL "使用 OpenSSL 作为 SSL 后端" ON) option(CURL_USE_MBEDTLS "使用 mbedTLS 作为 SSL 后端" OFF) option(BUILD_CURL_EXE "构建 curl 命令行工具" ON) option(BUILD_TESTING "构建测试" OFF)2:款平台适配
大量平台分支处理差异:
if(WIN32) target_link_libraries(curl PRIVATE ws2_32 crypt32) # Windows 系统库 elseif(APPLE) find_library(SECURITY_LIB Security) target_link_libraries(curl PRIVATE ${SECURITY_LIB}) elseif(UNIX) target_link_libraries(curl PRIVATE pthread) endif()3:灵活的依赖检测
同时支持 pkg-config 和手动查找两种方式,兼容不同系统:
find_package(PkgConfig QUIET) if(PKG_CONFIG_FOUND) pkg_check_modules(OPENSSL QUIET openssl) endif() if(NOT OPENSSL_FOUND) find_package(OpenSSL REQUIRED) endif()4:模块化目录结构
通过add_subdirectory拆分 lib、src、docs、tests、examples 等模块,结构清晰,便于维护。
3:Qt CMake项目分析
Qt 项目的 CMake 体现了GUI 项目的典型配置。
核心配置
cmake_minimum_required(VERSION 3.16) project(MyQtApp LANGUAGES CXX) # 开启 Qt 三大自动工具 set(CMAKE_AUTOMOC ON) # 自动处理 Q_OBJECT 宏的元对象编译 set(CMAKE_AUTOUIC ON) # 自动编译 .ui 界面文件 set(CMAKE_AUTORCC ON) # 自动编译 .qrc 资源文件 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 查找 Qt 模块 find_package(Qt6 COMPONENTS Widgets Network REQUIRED) add_executable(MyApp main.cpp mainwindow.cpp) # 链接 Qt 模块 target_link_libraries(MyApp PRIVATE Qt6::Widgets Qt6::Network)4:优秀项目的共性
- 明确的最低版本要求:
cmake_minimum_required放在第一行 - 标准的语言设置:明确 C/C++ 标准,关闭编译器扩展
- 目标优先的现代写法:所有配置挂载在目标上,避免全局命令
- 完善的跨平台处理:针对不同编译器、平台做适配
- 可配置的功能选项:通过 option 让用户按需裁剪
- 完整的发布流程:install、find_package 支持、测试、打包一应俱全
- 清晰的目录结构:模块化拆分,职责分明
3:CMake 常见问题排查手册
本节汇总开发中最常遇到的问题,每个问题给出「现象→原因→解决方案→预防方法」的完整排查思路。
1:VS Code 头文件下红线但编译正常
现象:代码能正常编译运行,但 VS Code 提示找不到头文件,标红波浪线。原因:VS Code 的 C/C++ 插件的智能提示和 CMake 是两套独立系统,默认不知道 CMake 配置的头文件路径。解决方案:
- 最佳方案:安装 CMake Tools 插件,它会自动同步 CMake 的头文件路径给 C/C++ 插件
- 手动配置:编辑
.vscode/c_cpp_properties.json,把头文件路径加入includePath - 快捷修复:鼠标移到红线上 → Quick Fix → Add to includePath预防:项目初始化时就安装 CMake Tools 插件。
2: 新增源文件后编译不包含
现象:新增了 cpp 文件,重新编译时报 undefined reference,或者根本没编译这个文件。原因:用了file(GLOB)收集源文件,它只在配置阶段执行一次,新增文件不会自动检测。解决方案:重新执行一次cmake ..配置,再编译。预防:
- 小型项目用 GLOB,记得新增文件后重新配置
- 大型项目显式列出所有源文件,更可靠
- CMake 3.12+ 可以加
CONFIGURE_DEPENDS参数:file(GLOB SRC CONFIGURE_DEPENDS "*.cpp"),构建时自动检查文件变化
3:find_package 找不到已安装的库
现象:库已经安装到系统,但find_package报错 Could NOT find。原因:库的安装路径不在 CMake 的默认搜索路径中。解决方案:
- 手动指定搜索前缀:
cmake .. -DCMAKE_PREFIX_PATH=/path/to/lib/install - 指定模块路径:
cmake .. -DCMAKE_MODULE_PATH=/path/to/cmake/modules - 开启查找调试:
cmake .. --debug-find-pkg=包名,查看具体查找过程 - 检查库是否安装了 CMake 配置文件(Config.cmake)或 Find 模块预防:安装库时用默认前缀
/usr/local,或者记录安装路径。
4:链接错误 undefined reference
现象:编译通过,链接时报 undefined reference toxxx()。原因:
- 没有链接对应的库
- 库的链接顺序不对(依赖库要放在被依赖库后面)
- 函数实现没有编译进去
- C/C++ 混合编程时 extern "C" 问题解决方案:
- 检查
target_link_libraries是否包含了对应的库 - 检查依赖的作用域是不是 PRIVATE 导致没传递给下游
- 检查源文件是否被加入到目标的源文件列表
- C++ 调用 C 函数时,头文件加
extern "C"包裹预防:用现代 CMake 的目标链接方式,自动处理依赖传递。
5:动态库运行时找不到
现象:编译正常,运行时报 error while loading shared libraries: libxxx.so: cannot open shared object file。原因:系统动态链接器找不到动态库的位置。解决方案:
- 临时解决:
export LD_LIBRARY_PATH=/path/to/lib:$LD_LIBRARY_PATH - 永久解决:把库路径加入
/etc/ld.so.conf,然后执行ldconfig - CMake 层面解决:设置可执行文件的 RPATH:
set_target_properties(myapp PROPERTIES INSTALL_RPATH "$ORIGIN/../lib" BUILD_RPATH "${CMAKE_BINARY_DIR}/lib" )
预防:发布时用$ORIGIN相对路径 RPATH,实现解压即运行。
6:CMake 缓存导致配置不生效
现象:改了 CMakeLists.txt 或者命令行参数,重新 cmake 后配置还是旧的。原因:CMakeCache.txt 中的缓存变量优先级高于 CMakeLists.txt 中的设置。解决方案:
- 删除 build 目录下的 CMakeCache.txt,重新配置
- 或者直接删除整个 build 目录,从头配置(最干净)
- 用
cmake -U 变量名取消指定缓存变量预防:修改缓存相关配置后,清理缓存再重新配置。
7:多线程编译报错但单线程正常
现象:make -j多线程编译报错,单线程make正常。原因:目标之间的依赖顺序没有声明,多线程时依赖的目标还没编译完就开始编译依赖它的目标。解决方案:用add_dependencies(目标A 目标B)显式声明 A 依赖 B,保证构建顺序。预防:所有有依赖关系的目标都显式声明依赖,不要依赖隐式的目录顺序。
8:安装后头文件 / 库路径不对
现象:执行 install 后,文件没有安装到预期的目录。原因:
- 没有用 GNUInstallDirs,硬写了错误的路径
CMAKE_INSTALL_PREFIX被缓存成旧值- install 命令的 DESTINATION 写错解决方案:
- 引入
include(GNUInstallDirs),用标准变量${CMAKE_INSTALL_BINDIR}等 - 清理缓存,重新指定安装前缀
- 检查 install 命令的路径配置预防:永远用 GNUInstallDirs 标准变量,不要硬写路径。
4:工程化最佳实践与扩展
1:现代CMake编码规范
- 目标优先:所有配置用
target_*命令挂载在目标上,禁止用全局的include_directories、link_directories、add_definitions - 作用域明确:明确指定 PRIVATE/PUBLIC/INTERFACE,不要省略
- 避免全局变量污染:尽量少用全局
set(),变量尽量局部化 - 命名规范:目标名、选项名统一用下划线或连字符,保持一致
- 路径规范:用
${CMAKE_CURRENT_SOURCE_DIR}、${CMAKE_CURRENT_BINARY_DIR}等内置变量,不要用相对路径 - 版本明确:明确指定
cmake_minimum_required、C/C++ 标准、库版本 - 注释清晰:复杂的配置、选项、自定义命令加注释说明
2:include 和 add_subdirectory深度对比
| 特性 | include() | add_subdirectory() |
|---|---|---|
| 作用 | 执行指定的 cmake 脚本文件 | 执行子目录下的 CMakeLists.txt |
| 变量作用域 | 继承当前作用域,直接读写当前变量 | 创建独立的目录作用域,变量隔离 |
| 源码路径变量 | 不改变CMAKE_CURRENT_SOURCE_DIR | 切换为子目录路径 |
| 构建目录 | 不创建对应的构建子目录 | 创建对应的构建子目录 |
| 适用场景 | 加载工具函数、公共配置、宏定义 | 构建源码子模块、子库 |
简单记忆:工具脚本用 include,源码模块用 add_subdirectory。
3:C++包管理器对比
CMake 原生配合包管理器可以大幅提升依赖管理效率:
| 包管理器 | 特点 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| vcpkg | 微软官方,跨平台 | 预编译二进制,Windows 支持好,库数量多 | 版本管理较弱 | Windows 优先的跨平台项目 |
| Conan | 功能最全,去中心化 | 严格的版本管理,二进制缓存,支持任意构建系统 | 学习曲线较陡 | 企业级大型跨平台项目 |
| Hunter | 纯 CMake 实现 | 和 CMake 深度集成,配置即下载 | 库数量较少,编译慢 | 纯 CMake 栈的中小型项目 |
| FetchContent | CMake 内置 | 无需额外工具,配置时下载源码 | 每次都要编译,没有二进制缓存 | 轻量依赖、小型项目 |
4:主流IDE与编译器支持矩阵
| IDE | 支持平台 | CMake 支持程度 | 最佳适用场景 |
|---|---|---|---|
| Visual Studio | Windows | 原生深度集成 | Windows 桌面、大型项目 |
| CLion | Windows/macOS/Linux | 深度集成,体验最佳 | 跨平台 C++ 开发 |
| VS Code | 全平台 | 插件支持,灵活度高 | 轻量开发、远程开发 |
| Xcode | macOS | 原生支持 | Apple 生态开发 |
| Qt Creator | 全平台 | 深度集成 | Qt 项目开发 |
| 编译器 | 平台 | CMake 支持 |
|---|---|---|
| GCC | Linux/macOS/Windows(MinGW) | 完美支持 |
| Clang | Linux/macOS/Windows | 完美支持 |
| MSVC | Windows | 完美支持 |
5:大型项目框架构建
- 1:目录结构标准化
项目根/ ├── CMakeLists.txt # 顶级配置,只做全局设置和子目录添加 ├── cmake/ # 自定义模块、工具脚本、工具链文件 ├── src/ # 主程序源码 ├── libs/ # 内部库模块 ├── include/ # 公开头文件 ├── tests/ # 单元测试、集成测试 ├── examples/ # 示例代码 ├── docs/ # 文档 └── third_party/ # 第三方依赖(可选)- 2:模块化拆分:每个库一个独立目录,有自己的 CMakeLists.txt,对外只暴露必要的头文件和目标
- 3:依赖分层:明确依赖关系,避免循环依赖,底层库不依赖上层
- 4:统一编译选项:通过接口库统一管理全项目的编译选项、警告等级、C++ 标准:
add_library(project_options INTERFACE) target_compile_features(project_options INTERFACE cxx_std_17) target_compile_options(project_options INTERFACE -Wall -Wextra) # 所有目标都链接这个接口库 target_link_libraries(myapp PRIVATE project_options)- 5:版本管理:用 project 命令的 VERSION 参数管理项目版本,自动生成版本头文件
- 6:交叉编译入门(拓展)
交叉编译是在一个平台上编译出另一个平台的可执行文件(比如在 Linux 上编译 ARM 平台的程序),CMake 通过工具链文件实现:
# toolchain-arm.cmake 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 /usr/arm-linux-gnueabihf) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)使用时指定工具链文件:
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchain-arm.cmake5:CMake总结
至此我们已经用三篇完整学完了整个 CMake 的全部内容,并补充了大量实战拓展:
第一篇:基础入门篇
- CMake 核心认知与环境搭建
- 单文件工程构建与完整命令行工作流
- 可执行文件的编译与安装
第二篇:工程核心篇
- 现代 CMake 目标 - 属性系统
- 静态库 / 动态库的制作、引用与发布
- 第三方依赖查找与集成
- CTest 测试与 CPack 打包
第三篇:进阶实战篇
- CMake 语法进阶与生成器表达式
- 真实开源项目 CMake 分析
- 常见问题排查手册
- 工程化最佳实践与拓展