第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版本对应的状态。版本越高,启用的新行为越多。

核心原则:cmake_minimum_required 不仅检查CMake版本够不够,更重要的是——它决定了整个构建的策略基线。

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_commandload_cache这些。CMake会给出警告,建议你替换成新命令。

注意:不要直接忽略警告。我曾经觉得「警告而已,能编译就行」,结果CMake 3.18直接移除了某个废弃命令,项目彻底崩了。

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 策略管理的最佳实践

经过这么多年的折腾,我总结了一套策略管理的「黄金法则」:

  1. 始终指定最低版本:每个CMakeLists.txt的第一行都写上cmake_minimum_required,不要省略。
  2. 显式设置关键策略:对于影响项目核心行为的策略,用cmake_policy(SET ... NEW)显式启用。
  3. 使用策略栈隔离影响:如果某个第三方模块需要旧行为,用PUSH/POP包裹,不要全局修改。
  4. 定期升级基线版本:我每半年检查一次CMake新版本,评估是否需要升级项目的最低版本。
  5. 写测试:在CI里用多个CMake版本跑构建,确保兼容性。
一句话总结:cmake_policy 不是摆设,它是CMake给你留的「后悔药」。用好它,升级不慌。

1.6 知识体系图

下面这张图展示了本章的核心逻辑,我画成了SVG,方便你理解:

CMake策略与兼容性知识体系 cmake_policy cmake_minimum_required 设定策略基线 SET / GET / PUSH / POP 精细控制策略状态 版本迁移流程 逐步升级、处理废弃 自动设置所有策略为对应版本状态 版本越高,启用新行为越多 PUSH/POP 隔离第三方模块 SET NEW 显式启用新行为 先升级cmake_minimum_required 逐个处理策略警告 核心原则:指定版本 → 控制策略 → 安全迁移 cmake_policy 是CMake给你的「后悔药」

嗯,这张图基本把本章的内容串起来了。你从cmake_policy出发,左边是版本声明,中间是策略操作,右边是迁移流程。三者缺一不可。

最后说一句:别怕策略。它不是你写代码的负担,而是保护你项目的铠甲。用好它,你的CMakeLists.txt会越来越健壮。


公众号:蓝海资料掘金营,微信 deep3321