第1章:策略与兼容性——cmake_policy、版本兼容、从旧版本迁移注意事项
各位同学,咱们今天聊聊CMake里一个特别容易被忽视、但坑又特别多的话题——策略与兼容性。
说实话,我刚开始用CMake那几年,根本不知道还有cmake_policy这回事。项目能编译就行,管它什么策略不策略。直到有一次,我从CMake 3.5升级到3.16,整个项目突然编译不过了……嗯,那天我加班到凌晨两点。
从那以后,我养成了一个习惯:每个CMakeLists.txt的第一行,一定是cmake_minimum_required。为什么?因为这一行决定了CMake的行为模式,说白了就是「你告诉CMake,按哪个版本的规矩来干活」。
1.1 为什么需要策略机制?
CMake团队一直在改进工具。有些改进是新增功能,不影响旧项目。但有些改进会改变已有行为——比如某个变量的默认值变了,或者某个命令的语法更严格了。
你想想看,如果CMake 3.20改了add_library的行为,而你项目里用了add_library,一升级就崩。这谁受得了?
所以CMake引入了策略(Policy)机制。每个策略都有一个名字,比如CMP0056。每个策略都有两种状态:NEW(新行为)和OLD(旧行为)。
当你写cmake_minimum_required(VERSION 3.10)时,CMake会自动把所有策略设置为3.10版本对应的状态。版本越高,启用的新行为越多。
1.2 cmake_policy 命令详解
cmake_policy命令有几种用法,我挑最常用的说。
设置策略范围
# 在当前作用域及子作用域中设置策略
cmake_policy(VERSION 3.12)
# 只设置当前目录及子目录
cmake_policy(SET CMP0056 NEW)
我个人习惯在项目根目录的CMakeLists.txt里用cmake_policy(VERSION ...),然后在子目录里尽量不碰策略,除非有特殊需求。
获取和检查策略
# 获取某个策略的当前状态
cmake_policy(GET CMP0056 result)
if(result STREQUAL "NEW")
message("CMP0056 已启用新行为")
endif()
# 推送和弹出策略栈
cmake_policy(PUSH)
cmake_policy(SET CMP0056 OLD)
# ... 做一些兼容旧版本的代码 ...
cmake_policy(POP)
这个PUSH/POP机制特别有用。我在迁移一个老项目时,某个第三方模块死活不兼容新策略,我就用PUSH把策略临时改成OLD,处理完再POP回来。干净利落。
cmake --help-policy CMP0056 查看详细说明。CMake的文档其实写得不错。
1.3 版本兼容性矩阵
不同CMake版本支持的策略数量不一样。我整理了一个简表,方便你对照:
| CMake版本 | 策略总数 | 典型新增策略 | 注意事项 |
|---|---|---|---|
| 3.0 | 约50个 | CMP0020~CMP0054 | 基础策略体系建立 |
| 3.10 | 约70个 | CMP0060~CMP0070 | 新增对CUDA的支持策略 |
| 3.16 | 约85个 | CMP0080~CMP0090 | Apple平台策略调整较多 |
| 3.20 | 约100个 | CMP0100~CMP0110 | 新增对Ninja的兼容策略 |
| 3.28 | 约110个 | CMP0120~CMP0130 | 对MSVC工具链有重大调整 |
你看,版本越高,策略越多。但别慌,大部分策略你根本用不到。只有当你遇到具体问题时,才需要去查对应的策略。
1.4 从旧版本迁移的注意事项
我曾经把一个CMake 2.8的项目迁移到3.20,那叫一个酸爽。这里分享几个血泪教训:
1.4.1 先升级cmake_minimum_required
别一上来就改代码。先把cmake_minimum_required改成目标版本,然后运行cmake,看它报什么错。
# 旧版本
cmake_minimum_required(VERSION 2.8)
# 新版本
cmake_minimum_required(VERSION 3.20)
CMake会告诉你哪些策略需要手动处理。一个一个来,别急。
1.4.2 处理废弃命令
有些命令在旧版本里能用,新版本里废弃了。比如build_command、load_cache这些。CMake会给出警告,建议你替换成新命令。
1.4.3 变量作用域的变化
CMake 3.0之后,变量作用域规则变得更严格了。比如set(var value PARENT_SCOPE的行为在CMP0024策略下有所不同。如果你发现子目录里的变量传不到父目录,八成是策略问题。
1.4.4 生成器表达式的普及
从CMake 3.0开始,生成器表达式(Generator Expressions)越来越重要。旧项目里可能到处都是if(WIN32)这种硬编码判断,新版本更推荐用$<$<PLATFORM_ID:Windows>:...>这种生成器表达式。
我个人建议:迁移时不要一次性全改。先把项目跑起来,再逐步优化。
1.5 策略管理的最佳实践
经过这么多年的折腾,我总结了一套策略管理的「黄金法则」:
- 始终指定最低版本:每个CMakeLists.txt的第一行都写上
cmake_minimum_required,不要省略。 - 显式设置关键策略:对于影响项目核心行为的策略,用
cmake_policy(SET ... NEW)显式启用。 - 使用策略栈隔离影响:如果某个第三方模块需要旧行为,用
PUSH/POP包裹,不要全局修改。 - 定期升级基线版本:我每半年检查一次CMake新版本,评估是否需要升级项目的最低版本。
- 写测试:在CI里用多个CMake版本跑构建,确保兼容性。
1.6 知识体系图
下面这张图展示了本章的核心逻辑,我画成了SVG,方便你理解:
嗯,这张图基本把本章的内容串起来了。你从cmake_policy出发,左边是版本声明,中间是策略操作,右边是迁移流程。三者缺一不可。
最后说一句:别怕策略。它不是你写代码的负担,而是保护你项目的铠甲。用好它,你的CMakeLists.txt会越来越健壮。
公众号:蓝海资料掘金营,微信 deep3321