目录
一.CMake策略简要理解
1.1.第一阶段:童年时期(旧行为,The "Old Way")
1.2.第二阶段:成长与改进(引入新行为,The "New Way")
1.3.第三阶段:与不同项目打交道(项目应对,Project Management)
二.CMake的策略编号——CMPXXXX 编号
三.如何高效地查阅和理解一个策略?
四.设置/查询策略——cmake_policy
4.1.命令用法介绍
4. cmake_policy(PUSH) 和 cmake_policy(POP)
4.2.使用实例1——V_proj < V_policy
4.3.使用示例2——V_proj >= V_policy
4.4.使用示例3——cmake_policy(VERSION )
4.5.使用示例4—— cmake_policy(PUSH) 和 cmake_policy(POP)
一.CMake策略简要理解
想象一下 CMake 不是一个软件工具,而是一个在不断成长和学习的语言翻译官。他的工作是把 CMakeLists.txt
这份“项目说明书”(用一种特殊的方言写成)翻译成 Makefile
或 Visual Studio
项目文件等“施工图纸”。
1.1.第一阶段:童年时期(旧行为,The "Old Way")
在 CMake 3.0 这个“童年时期”,我们的小翻译官在翻译 command_a
这个句子时,掌握得不是很好。他的理解有点死板和模糊(缺陷或歧义)。
-
具体情景:比如,
command_a
的本意是“把苹果和梨放进篮子”。但小 CMake 3.0 的理解是:“如果篮子里有东西,就把苹果和梨放在它旁边;如果篮子空着,就放进去”。这个规则很复杂,容易出错,而且不同情况下结果可能不一致。 -
项目们的适应:很多早期的项目(“老雇主”)已经摸透了小翻译官的这个古怪脾气。他们的说明书里会特意写上:“先确保篮子是空的,然后再下
command_a
指令”。这样,虽然翻译官的规则有缺陷,但项目最终也能得到正确的“施工图纸”。
此时的状态:世界运转正常,老雇主们和年轻的翻译官配合默契,尽管方式有点别扭。
1.2.第二阶段:成长与改进(引入新行为,The "New Way")
时间跳到 CMake 3.20 时代。我们的小翻译官已经长大,经验丰富,他发现年轻时对 command_a
的理解太复杂且容易出错,决定进行一次改进。
-
做出决定:他意识到,
command_a
最合理、最不容易出错的理解应该是:“无论篮子是否为空,都清空篮子,然后把苹果和梨放进去”。这个新规则简单、清晰、一致。 -
关键的兼容性决策:但是,成熟的 CMake 翻译官面临一个难题:
“如果我直接改用新的理解方式,所有那些已经习惯了旧规则的老雇主的说明书,不就会被我‘译错’了吗?他们的项目就会构建失败!”
-
诞生了“策略”机制:为了解决这个难题,智慧的 CMake 发明了一个绝妙的办法——“策略开关”。他给这个关于
command_a
的改进方案起了一个唯一的编号:CMP0999
。-
OLD
开关:当这个开关打开时,他继续使用 3.0 时代那个有缺陷的旧规则进行翻译。 -
NEW
开关:当这个开关打开时,他就使用新的、更合理的规则进行翻译。 -
这个
CMP0999
策略随着 CMake 3.20 版本一同发布。
-
此时的状态:翻译官变得更聪明了,但他身上多了一个名为 CMP0999
的新开关,默认状态是“向下兼容”。
1.3.第三阶段:与不同项目打交道(项目应对,Project Management)
现在,成年的 CMake 3.20 翻译官开始为不同的项目工作。
当 CMake 解释器(无论其自身版本是 3.5, 3.20 还是更高)开始处理一个项目时,它会遵循以下步骤来设置每一个策略 CMPXXXX
的默认状态:
步骤一:确定基准版本
决策过程始于获取两个至关重要的版本号:
-
解析项目要求的最低版本 (
V_proj
):解释器首先会在项目的 CMakeLists.txt 文件中寻找cmake_minimum_required(VERSION <min-version>)命令。该命令中指定的<min-version>(例如2.8.12或3.20)被定义为V_proj。这个版本号代表了项目开发者向 CMake 声明的**最低兼容性要求**,其隐含的语义是:“我的项目代码是按照 CMakeV_proj 版本的行为规范来编写的,并承诺能在该版本及更高版本上正常工作。” -
查询策略的引入版本 (
V_policy
):
每一个具体的策略CMPXXXX
在 CMake 的源代码和历史中都有一个明确的属性,记录了它是从哪个 CMake 版本开始被引入的。这个版本号被定义为V_policy
。例如,策略CMP0048
(与project()
命令相关)是在 3.0.0 版本引入的;策略CMP0077
(与option()
命令相关)是在 3.13.0 版本引入的。
步骤二:基于版本比较的自动决策
对于每一个独立的策略 CMPXXXX
,解释器会将上述两个版本号进行比较,并应用一条核心规则:
-
情况一:如果
V_proj
<V_policy
-
决策结果:将该策略
CMPXXXX
的初始状态设置为OLD或者不设置
。 -
决策逻辑1——设置为OLD:项目所要求的最低 CMake 版本 (
V_proj
) 早于该策略被引入的版本 (V_policy
)。这意味着该项目是在完全不知道此策略存在的时代编写的。如果贸然启用新行为 (NEW
),极有可能破坏项目的构建过程,因为项目的代码期望的是旧的行为。因此,CMake 为了无条件保证向后兼容性,会自动选择OLD
行为,模拟一个“过去的时代”,确保老项目能继续构建。 -
决策逻辑2——不设置:事实上呢,很多版本的CMake都会在
V_proj
<V_policy
时不去设置策略CMPXXXX的初始状态,我们查询CMPXXXX的初始状态也是空,其实这个时候CMake底层还是采取了OLD行为的. -
也就是说,如果
V_proj
<V_policy,
策略CMPXXXX
的初始行为是设置为OLD
-
-
情况二:如果
V_proj
>=V_policy
-
决策结果:将该策略
CMPXXXX
的初始状态设置为NEW
。 -
决策逻辑:项目要求的最低 CMake 版本 (
V_proj
) 等于或晚于该策略被引入的版本 (V_policy
)。这意味着项目开发者声明他们的项目兼容于一个已经知晓该策略存在的 CMake 版本。CMake 据此认为,项目开发者有责任了解并适应自V_proj
以来所有的新行为。因此,默认启用NEW
行为是合理的,这推动了项目向更现代、更一致的行为迁移,也是 CMake 团队所鼓励的做法。
-
这个基于版本的自动决策是 CMake 策略机制最核心、最自动化的部分。它确保了用 cmake_minimum_required(VERSION 2.8.12)
编写的项目,在完全不做任何修改的情况下,依然能在 CMake 3.28 上成功编译,因为所有在 2.8.12 之后引入的策略都会被自动设置为 OLD
状态。
步骤三:处理用户的显式指令(最高优先级覆盖)
在上述自动决策过程之后,CMake 解释器会继续解析 CMakeLists.txt
文件中的内容。
-
覆盖检查:如果在代码中遇到了一个显式的策略设置命令:
cmake_policy(SET CMPXXXX NEW) # 或 OLD
-
最终决策:这条显式指令拥有绝对的、最高的优先级。它会立即覆盖第二阶段中通过版本比较得出的任何默认状态。
这个步骤赋予了项目开发者最终的控制权。它允许开发者打破基于版本的自动决策,适用于以下场景:
-
前瞻性启用:即使项目声明了一个较低的
V_proj
,但如果开发者确认其代码与某个新策略的NEW
行为兼容,可以主动启用它来获得好处(如更清晰的警告或功能)。 -
保守性回退:即使项目声明了一个较高的
V_proj
,但如果发现某个新策略的NEW
行为确实导致了问题,可以强制回退到OLD
行为作为临时解决方案。
二.CMake的策略编号——CMPXXXX 编号
CMPXXXX
是一个策略(Policy)的唯一标识符。策略编号的格式是固定的:CMP
前缀加上一个 四位数字,例如 CMP0000
, CMP0123
, CMP1000
。
-
CMP
: 是 CMake Policy 的缩写。 -
XXXX
: 是一个四位数字编号(例如0001
,0084
,1000
等),每个编号对应一个非常具体、明确的 CMake 行为变化。这个四位数字的编号并非随意分配,它隐含了策略被引入的时间顺序和一定的功能范畴。
编号的规律和解读
1. 顺序增长
最核心的规律是:策略编号大致上是按引入时间的先后顺序递增的。
-
CMP0000
:这是一个特殊的策略,在 CMake 2.6.0 中引入,用于控制LINK_INTERFACE_LIBRARIES
的属性是否默认被初始化。它是编号的起点。 -
CMP00XX
(如CMP0048
):这些通常是 CMake 3.0 时代或更早引入的策略。数字较小,意味着它们处理的是比较古老的行为变更。 -
CMP01XX
-CMP02XX
:这些策略大多在 CMake 3.x 时代的中期被引入(例如 3.5 - 3.15 左右)。 -
CMP03XX
-CMP04XX
:这些是相对较新的策略,在近期的 CMake 3.x 版本中引入。 -
CMP1000+
:当编号突破CMP0999
后,会进入四位数领域(如CMP1000
,CMP1001
)。这通常意味着 CMake 的发展进入了一个新的阶段,或者有大量的新策略被引入。例如,CMP1000
是关于cmake -E tar
命令的行为。
如何验证?
你可以使用命令 cmake --help-policy-list
,输出的策略列表默认就是按照编号排序的。从上到下浏览,你就像在翻阅一部 CMake 的演进史。
2. 数字本身没有特定含义,但可能有粗略的“系列”感
虽然编号主要是顺序的,但有时在特定版本附近引入的一批策略可能会处理相关的主题。
-
例如,在 CMake 3.12 附近,有一批策略(如
CMP0070
到CMP0080
左右)大量处理的是与生成器表达式(Generator Expressions)、try_compile
行为、以及现代 CMake 目标特性相关的问题。 -
但这只是一个粗略的趋势,并非严格的规则。不能绝对地说
CMP07XX
就一定是关于某个特定功能的。
3. 前导零(Leading Zeros)
编号是四位定长的。所以 CMP0048
是第 48 个策略,而不是第 480 个。CMP1000
则是第 1000 个策略。前导零只是为了格式整齐。
三.如何高效地查阅和理解一个策略?
当你遇到一个陌生的策略编号(比如来自一个编译警告),以下是最高效的处理流程:
-
使用命令行查询(最快最直接)
在终端中直接运行:cmake --help-policy CMPXXXX
这会立刻显示出我们之前讨论过的完整文档,包括它的引入版本、新旧行为差异和详细解释。这是诊断和解决问题的最权威来源。
-
查阅官方在线手册
所有策略都按编号顺序列在官方的 CMake 策略手册中。大家可以去官网进行查询:cmake-policies(7) — CMake 4.1.1 Documentation -
理解引入版本是关键
策略文档中一定会明确指出该策略是从哪个 CMake 版本开始引入的(e.g.,Introduced in CMake version 3.0.
)。-
这极其重要:它直接告诉你这个行为变化发生在哪个历史节点。
-
如果你的
cmake_minimum_required(VERSION X.Y)
中的X.Y
早于策略的引入版本,那么 CMake 会为了兼容性而默认将该策略设置为OLD
。 -
如果你的
cmake_minimum_required(VERSION X.Y)
中的X.Y
等于或晚于策略的引入版本,CMake 通常会默认将其设置为NEW
。
-
举例说明
假设你遇到了 CMP0082
这个策略的警告。
-
查询:
cmake --help-policy CMP0082
-
阅读摘要:你会发现它是关于
Installation Step
中GoogleTest
(gtest)的命令是否可用的问题。 -
查看引入版本:
Introduced in CMake version 3.14.
。这意味着这个行为变化发生在 CMake 3.14。 -
分析你的项目:
-
如果你的项目声明
cmake_minimum_required(VERSION 3.10)
,因为 3.10 < 3.14,CMake 会使用OLD
行为来保证兼容,但会给你警告。 -
如果你的项目声明
cmake_minimum_required(VERSION 3.14)
或更高,CMake 会默认使用NEW
行为。
-
-
做出决策:
-
如果项目不需要兼容旧版 gtest:你可以安全地
cmake_policy(SET CMP0082 NEW)
来消除警告并采用新行为。 -
如果项目严重依赖旧行为:你可以显式地
cmake_policy(SET CMP0082 OLD)
来沉默警告并维持旧行为(但需知这是临时措施,因为旧行为终将被废弃)。
-
我们来查询一个
什么意思呢?我们来仔细看看
CMP0048 核心摘要
这个策略控制着 project()
命令如何管理与版本相关的变量(如 PROJECT_VERSION
)。它引入了新旧行为之间的一个关键变化,主要影响那些在调用 project()
之前或之后自行设置了版本变量的项目。
详细解释
1. 背景与问题所在
CMake version 3.0 introduced the ``VERSION`` option of the ``project()``
command to specify a project version as well as the name.
-
意思是:在 CMake 3.0 之前,
project()
命令只能设置项目名。从 3.0 版本开始,project()
命令增加了一个VERSION
选项,允许你同时指定项目的版本号,例如:project(MyAwesomeApp VERSION 1.2.3)
。
In order to keep
``PROJECT_VERSION`` and related variables consistent with variable
``PROJECT_NAME`` it is necessary to set the ``VERSION`` variables
to the empty string when no ``VERSION`` is given to ``project()``.
-
意思是:为了保持行为的一致性,如果一个
project()
命令没有提供VERSION
参数,CMake 认为你需要将版本变量设置为空字符串。这样,PROJECT_NAME
(总有值)和PROJECT_VERSION
(可能为空)的状态就是明确和可预测的。
However, this can change behavior for existing projects that set ``VERSION``
variables themselves since ``project()`` may now clear them.
-
这才是关键矛盾:这个新的“清空”行为会破坏那些为旧版 CMake(3.0 之前)编写的项目。想象一下这个场景:
# 一个 CMake 2.8 的项目 set(PROJECT_VERSION "2.0") # 开发者自己手动设置版本变量 project(MyOldProject) # 旧版 project() 不关心 VERSION 变量,所以 PROJECT_VERSION 仍然是 "2.0"
在 CMake 3.0+ 下,如果新行为生效,
project(MyOldProject)
会因为自己没有VERSION
参数而把PROJECT_VERSION
清空!这显然不是开发者想要的结果。
2. 新旧行为(The OLD and NEW)
The ``OLD`` behavior for this policy is to leave ``VERSION`` variables untouched.
-
OLD 行为(默认,为了兼容):
project()
命令不会去主动设置或清空任何版本变量(如PROJECT_VERSION
,PROJECT_VERSION_MAJOR
等)。这些变量保持它们之前的值(如果被设置过的话),或者未定义状态。 -
这是为了确保那些在
project()
调用前手动设置了版本变量的老项目能继续正常工作。
The ``NEW`` behavior for this policy is to set ``VERSION`` as documented by the
``project()`` command.
-
NEW 行为(推荐,更一致):
project()
命令会严格按照其文档来管理版本变量。-
如果调用
project(MyProj VERSION 1.2.3)
,它会正常设置PROJECT_VERSION=1.2.3
,PROJECT_VERSION_MAJOR=1
等。 -
如果调用
project(MyProj)
(没有VERSION
参数),它会将这些版本变量设置为空字符串,以确保行为明确。
-
3. 版本与状态
This policy was introduced in CMake version 3.0.
-
引入版本:这个策略是伴随着它所管理的那个特性(
project(VERSION)
)一起在 CMake 3.0 中出现的。
CMake version 3.28.3 warns when the policy is not set and uses ``OLD`` behavior.
-
当前行为:即使你使用的是很新的 CMake(如 3.28.3),如果你没有明确设置这个策略,CMake 为了最大限度地保证兼容性,仍然会采用
OLD
行为。但同时,它会发出警告,提醒你存在这个策略未设置的潜在问题。
Use the ``cmake_policy()`` command to set it to ``OLD`` or ``NEW`` explicitly.
-
解决方案:你应该在你的
CMakeLists.txt
中明确设置这个策略的状态,以避免警告并明确指定你期望的行为。例如:cmake_policy(SET CMP0048 NEW) # 推荐:采用新的、一致的行为 # 或者 cmake_policy(SET CMP0048 OLD) # 明确要求保持旧行为(通常是为了兼容老脚本)
4. 重要警告
.. note:: The ``OLD`` behavior of a policy is ``deprecated by definition`` and may be removed in a future version of CMake.
-
终极建议:这是一个非常重要的提示。它告诉你,
OLD
行为从定义上就被认为是“已废弃的”。虽然现在还能用,但未来的某个 CMake 版本可能会彻底移除OLD
行为,只保留NEW
行为。到那时,如果你还在依赖OLD
行为,你的项目将会构建失败。 -
因此,CMake 官方强烈建议你尽快将项目迁移到
NEW
行为上。
四.设置/查询策略——cmake_policy
4.1.命令用法介绍
cmake_policy
命令的唯一目的,就是管理我们之前讨论的策略(Policies),即那些 CMPXXXX
开关。它允许你查询、设置、保存和恢复这些策略的状态。
cmake_policy
有多个子命令,每个都有其特定的用途。我们将逐一分解。
1. cmake_policy(SET CMP<NNNN> NEW)
作用:显式地将单个策略设置为 NEW
或 OLD
行为。
-
何时使用:
-
当 CMake 产生策略警告,提示你
CMPXXXX is not set
时,你用它来明确指定行为,从而消除警告。 -
当你了解一个策略的新旧行为差异,并主动决定你的项目要采用哪一种时。
-
-
示例:
# 启用 CMP0077 的新行为(option() 命令尊重普通变量) cmake_policy(SET CMP0077 NEW)# 强制保持 CMP0060 的旧行为(可能因为某些第三方代码需要) cmake_policy(SET CMP0060 OLD)
-
重要提示:这个设置的影响范围取决于你在哪里调用它。在顶层
CMakeLists.txt
中设置,会影响整个项目。在函数或宏中设置,通常只在该函数或宏的范围内有效(除非使用其他手段)。
2. cmake_policy(GET CMP<NNNN> <variable>)
作用:查询指定策略的当前状态,并将结果(OLD
、NEW
或空字符串)存储到一个变量中。
-
何时使用:通常用于调试或编写非常复杂的、需要根据当前策略状态来动态调整行为的 CMake 脚本。日常项目中较少使用。
-
示例:
#把策略 CMP0048 当前的状态(OLD / NEW / 或未设置)存放到变量 current_cmp0048 里。 cmake_policy(GET CMP0048 current_cmp0048) message(STATUS "The state of CMP0048 is: ${current_cmp0048}")
3. cmake_policy(VERSION <version>)
作用:请求 CMake 将策略状态批量设置为指定版本所定义的行为。
-
它与
cmake_minimum_required(VERSION ...)
的关系:-
cmake_minimum_required(VERSION X.Y)
:声明兼容性。“我的项目最低需要 CMake X.Y,请保证在这个版本上的行为。” -
cmake_policy(VERSION X.Y)
:请求行为。“请将我的项目策略设置为尽可能接近 CMake X.Y 的行为。”
-
-
关键限制:你通过
cmake_policy(VERSION)
指定的版本号 不能低于cmake_minimum_required
指定的版本号。它只能用来启用更新的行为,而不能回溯更旧的行为。 -
何时使用:当你希望项目利用新版本 CMake 的特性,但又不想逐个去设置几十个策略时。这是一种“批量现代化”的操作。
注意:CMake 里的 3.15 和 3.5 不是小数(decimal),而是 版本号 (version number)。
版本号比较规则是 逐段比较,也就是:
3.5
实际上等于3.5.0
3.15
实际上等于3.15.0
比较时先看 主版本号(major),再看 次版本号(minor),再看 补丁号(patch):
3.5.0 vs 3.15.0↑ ↑ major 相同 = 3 minor 比较 5 < 15 → 所以 3.5.0 < 3.15.0
所以 3.15 > 3.5。
就好比 Python 3.10 其实比 Python 3.9 新,但如果你把它当小数看就会误以为 3.10 < 3.9,这是一个经典的坑。
典型模式:
cmake_minimum_required(VERSION 3.5) # 保证能在 3.5 上构建
cmake_policy(VERSION 3.15) # 但启用 3.5 到 3.15 之间所有策略的 NEW 行为
这意味着你的代码本身是兼容 3.5 的写法,但构建环境如果提供了 3.15 的 CMake,就能享受到 3.15 的新行为和改进。
CMake 解释器会这样执行:
-
建立基准 (
V_proj
):第一行命令将V_proj
永久设置为 3.5。这意味着:-
所有在 CMake 3.5 之后引入的策略(即
V_policy > 3.5
),其默认状态将被设置为OLD
(为了兼容性)。 -
所有在 CMake 3.5 及之前引入的策略,其默认状态将被设置为
NEW
。
-
-
执行批量覆盖:第二行命令
cmake_policy(VERSION 3.15)
开始工作。它对每一个策略进行如下判断:-
如果 该策略的
V_policy
满足:3.5< V_policy ≤ 3.15,那么 将这个策略的状态从默认的OLD
覆盖为NEW
。因为当执行cmake_policy(VERSION 3.15)
之后,CMake 会认为“既然用户要求模拟 3.15 版本的行为”,那么所有 在 3.15 及以前引入的策略(也就是这一区间的策略)就应该启用新行为,于是 CMake 会把它们的状态 从 OLD 覆盖为 NEW。 -
如果 该策略的
V_policy
> 3.15,那么这条命令不处理它,它保持其默认状态(在这种情况下是OLD
,因为V_proj=3.5 < V_policy
)。 -
如果 该策略的
V_policy
≤ 3.5,它已经被V_proj
默认设置为NEW
了,这条命令不会改变它(保持NEW
)。
-
所以,它的效果是: 将策略状态从“模仿 CMake 3.5 的行为”提升为“模仿 CMake 3.15 的行为”,但仅限于那些在 3.5 到 3.15 之间引入的策略。
策略引入版本 V_policy | 默认状态 (由 V_proj=3.5 决定) | cmake_policy(VERSION 3.15) 之后的状态 | 说明 |
---|---|---|---|
V_policy ≤ 3.5 | NEW | NEW | 项目声明的版本已经覆盖这些策略 → 默认就是 NEW,不受 cmake_policy(VERSION 3.15) 影响 |
3.5 < V_policy ≤ 3.15 | OLD | NEW | 默认是 OLD(兼容旧项目),但 cmake_policy(VERSION 3.15) 会把它们提升为 NEW,这是该指令的核心作用对象 |
V_policy > 3.15 | OLD | OLD | 默认是 OLD(项目声明版本太旧),而 cmake_policy(VERSION 3.15) 无法提前启用未来的策略,所以保持 OLD |
4. cmake_policy(PUSH)
和 cmake_policy(POP)
作用:这对命令用于管理一个策略栈,是实现策略作用域化的核心工具。
-
PUSH
:将当前所有策略的状态压入栈中保存起来。 -
POP
:将栈顶的策略状态弹出,并恢复所有策略到那个状态。 -
何时使用:这是极其重要的最佳实践,用于封装策略修改,避免产生“副作用”。
-
在你写的宏(macro) 或函数(function) 内部。
-
当你
include()
一个可能修改策略的外部.cmake
脚本文件时。 -
在任何你只想临时改变策略,之后又想恢复原状的代码块中。
-
-
示例:
function(my_complex_function)cmake_policy(PUSH) # 1. 进入函数,先保存当前的策略状态# 2. 在函数内部临时更改一些策略cmake_policy(SET CMP9999 NEW)cmake_policy(SET CMP0001 NEW)# ... 执行一些需要特定策略状态的复杂操作 ...cmake_policy(POP) # 3. 离开函数前,恢复调用函数前的策略状态 endfunction()
为什么这样做? 这确保了当你调用
my_complex_function()
时,无论它内部如何折腾策略,都不会影响到调用它之前的那个环境的策略设置。这就是良好的封装。这与
block(SCOPE_FOR POLICIES)
的关系:block(SCOPE_FOR POLICIES)
在背后自动为你执行了PUSH
和POP
操作,是一种更简洁的语法糖。
4.2.使用实例1——V_proj < V_policy
首先,我们需要知道,当前我的cmake版本号是
接下来我们示例测试的策略编号是CMP0048,这个策略被引入时是cmake 3.0.
那么现在我们就看看例子
项目结构
test/
└── CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 2.8)# 在设置之前,先获取策略 CMP0048 的状态
cmake_policy(GET CMP0048 current_cmp0048)
message("初始 CMP0048 状态: ${current_cmp0048}")# 显式设置策略为 NEW
cmake_policy(SET CMP0048 NEW)# 再次获取策略状态
cmake_policy(GET CMP0048 current_cmp0048)
message("设置 NEW 之后 CMP0048 状态: ${current_cmp0048}")# 下面演示 project() 和 PROJECT_VERSION 的行为
project(MyProject) # 没有 VERSION 参数,NEW 行为会清空 PROJECT_VERSIONmessage("调用 project() 之后 PROJECT_VERSION: '${PROJECT_VERSION}'")# 手动设置版本
set(PROJECT_VERSION "2.0")
message("手动设置 PROJECT_VERSION 之后: ${PROJECT_VERSION}")
其实我们可以执行下面这个来一键搭建出这个目录结构
mkdir -p test && cat > test/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 2.8)# 在设置之前,先获取策略 CMP0048 的状态
cmake_policy(GET CMP0048 current_cmp0048)
message("初始 CMP0048 状态: ${current_cmp0048}")# 显式设置策略为 NEW
cmake_policy(SET CMP0048 NEW)# 再次获取策略状态
cmake_policy(GET CMP0048 current_cmp0048)
message("设置 NEW 之后 CMP0048 状态: ${current_cmp0048}")# 下面演示 project() 和 PROJECT_VERSION 的行为
project(MyProject) # 没有 VERSION 参数,NEW 行为会清空 PROJECT_VERSIONmessage("调用 project() 之后 PROJECT_VERSION: '${PROJECT_VERSION}'")# 手动设置版本
set(PROJECT_VERSION "2.0")
message("手动设置 PROJECT_VERSION 之后: ${PROJECT_VERSION}")
EOF
接下来我们就来构建一下项目
mkdir build && cd build && cmake ..
首先我们看
CMP0048
的 NEW
行为规定:如果 project()
命令没有 VERSION
参数,则 PROJECT_VERSION
等变量应为空。您的 project(MyProject)
没有指定版本,所以之后 PROJECT_VERSION
是空字符串 ''
,这符合 NEW
行为的预期,也验证了设置生效了。
接下来,
注意:按照我们的推理,过程应该是下面这样子的
- 从cmake_minimum_required(VERSION 2.8)得知V_proj =2.8
- 从cmp0048引入时的版本号是3.0,得知V_policy=3.0
- V_proj < V_policy,所以默认采取OLD或者不指定
但是我们仔细看一下
在 CMake 3.28 的机器里:
-
CMP0048
策略是 CMake 3.0 引入的。 -
当你写
cmake_minimum_required(VERSION 2.8)
时,表示“我要兼容 2.8 的行为”。 -
结果:CMake 不会默认设置 CMP0048,所以
cmake_policy(GET CMP0048 current_cmp0048)
得到的是 空字符串。
也就是说:
👉 cmake_minimum_required(VERSION 2.8)
太老,CMake 根本没法替你决定 CMP0048 用 OLD 还是 NEW,于是它保持 未定义 状态。
嗯??未定义状态?我们来好好捋捋
-
每个策略(比如
CMP0048
)都有两种明确状态:OLD
/NEW
。 -
但是当你用的 CMake 比该策略更老时(例如:
cmake_minimum_required(VERSION 2.8)
,而CMP0048
是在 3.0 才引入的),解释器就不知道该策略该走哪边,于是它保持 未设置。 -
未设置状态下的规则:
-
cmake_policy(GET CMP0048 var)
→ 得到 空字符串(啥也没有)。 -
运行时 → CMake 默认退回到 OLD 行为(保证兼容性),但这跟你显式写
SET CMP0048 OLD
不是一回事。
-
换句话说:
👉 “未设置” = 表面上跑 OLD 行为,但内部 CMake 还记着这个策略没被项目明确确认过。
我们可以看个例子看看未定义状态是不是默认采取OLD?
我们把上面的CMakeLists.txt换成下面这个,然后再重新构建一下项目看看
cmake_minimum_required(VERSION 2.8) # 早于 CMP0048 的引入版本# 尝试获取 CMP0048
cmake_policy(GET CMP0048 var)
message("第一次获取 CMP0048: '${var}'")project(TestUnset)# 先设置一个版本号
set(PROJECT_VERSION "2.0")# 再次调用 project()
project(TestUnset2)# 打印结果
message("PROJECT_VERSION after second project(): '${PROJECT_VERSION}'")# 显式设置 CMP0048 为 NEW,再调用 project()
cmake_policy(SET CMP0048 NEW)# 尝试获取 CMP0048
cmake_policy(GET CMP0048 var)
message("第二次获取 CMP0048: '${var}'")project(TestUnset3)
message("PROJECT_VERSION after NEW policy: '${PROJECT_VERSION}'")
这个实验结果完全说明了「未设置 = OLD 行为」的机制。
我们来逐行拆解:
第一次获取 CMP0048: ''
👉 cmake_policy(GET CMP0048 var)
返回空字符串,说明 CMP0048 处于未设置状态。
这是因为你 cmake_minimum_required(VERSION 2.8)
,而 CMP0048 是在 CMake 3.0 引入的,2.8 的项目不认识它,所以不设置。
CMake Warning (dev) ... Policy CMP0048 is not set ...
👉 CMake 明确告诉你:CMP0048 没有设置(unset),并且提示你用 cmake_policy(SET ...)
来决定 NEW / OLD。
PROJECT_VERSION after second project(): '2.0'
👉 这就是 未设置 → OLD 行为 的效果:
-
OLD 行为 =
project()
不会清空已有的PROJECT_VERSION
。 -
所以
"2.0"
被保留了下来。
如果是 NEW 行为,此时应该被清空,结果是 ''
。
第二次获取 CMP0048: 'NEW'
PROJECT_VERSION after NEW policy: ''
👉 你在脚本里显式执行了
cmake_policy(SET CMP0048 NEW)
所以第二次获取时就是 NEW,而 project()
在 NEW 行为下会清空版本号 → 输出 ''
。
✅ 总结
-
未设置状态:不能通过
cmake_policy(GET ...)
获取(空字符串),但运行时会采用 OLD 行为,保证兼容老版本 CMake。 -
显式设置为 NEW:行为立刻切换为新语义。
所以实验流程和输出完全符合 CMake 的机制。
4.3.使用示例2——V_proj >= V_policy
V_proj >V_policy的情况
首先,我们需要知道,当前我的cmake版本号是
接下来我们示例测试的策略编号是CMP0048,这个策略被引入时是cmake 3.0.
那么现在我们就看看例子
项目结构
test/
└── CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.5)# 在设置之前,先获取策略 CMP0048 的状态
cmake_policy(GET CMP0048 current_cmp0048)
message("初始 CMP0048 状态: ${current_cmp0048}")# 显式设置策略为 OLD
cmake_policy(SET CMP0048 OLD)# 再次获取策略状态
cmake_policy(GET CMP0048 current_cmp0048)
message("设置 OLD 之后 CMP0048 状态: ${current_cmp0048}")# 先手动设置 PROJECT_VERSION
set(PROJECT_VERSION "2.0")# 调用 project()(没有 VERSION 参数,OLD 行为会保留 PROJECT_VERSION)
project(MyProject)message("调用 project() 之后 PROJECT_VERSION: '${PROJECT_VERSION}'")
其实我们可以执行下面这个来一键搭建出这个目录结构
mkdir -p test && cat > test/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.5)# 在设置之前,先获取策略 CMP0048 的状态
cmake_policy(GET CMP0048 current_cmp0048)
message("初始 CMP0048 状态: ${current_cmp0048}")# 显式设置策略为 OLD
cmake_policy(SET CMP0048 OLD)# 再次获取策略状态
cmake_policy(GET CMP0048 current_cmp0048)
message("设置 OLD 之后 CMP0048 状态: ${current_cmp0048}")# 先手动设置 PROJECT_VERSION
set(PROJECT_VERSION "2.0")# 调用 project()(没有 VERSION 参数,OLD 行为会保留 PROJECT_VERSION)
project(MyProject)message("调用 project() 之后 PROJECT_VERSION: '${PROJECT_VERSION}'")
EOF
接下来我们就来构建一下项目
mkdir build && cd build && cmake ..
这很符合我们的预期吧!!!
V_proj = V_policy的情况
我们接下来看看相同版本的,我们只需要将上面的CMakeLists.txt进行小修改即可
cmake_minimum_required(VERSION 3.0)# 在设置之前,先获取策略 CMP0048 的状态
cmake_policy(GET CMP0048 current_cmp0048)
message("初始 CMP0048 状态: ${current_cmp0048}")# 显式设置策略为 OLD
cmake_policy(SET CMP0048 OLD)# 再次获取策略状态
cmake_policy(GET CMP0048 current_cmp0048)
message("设置 OLD 之后 CMP0048 状态: ${current_cmp0048}")# 先手动设置 PROJECT_VERSION
set(PROJECT_VERSION "2.0")# 调用 project()(没有 VERSION 参数,OLD 行为会保留 PROJECT_VERSION)
project(MyProject)message("调用 project() 之后 PROJECT_VERSION: '${PROJECT_VERSION}'")
还是很符合我们的预期的!!
4.4.使用示例3——cmake_policy(VERSION <version>)
项目结构
test/
└── CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(TwoPoliciesDemo)# 设置策略到 2.8
cmake_policy(VERSION 2.8)cmake_policy(GET CMP0046 state)
message("在 VERSION 2.8 下 CMP0046 状态: '${state}'")cmake_policy(GET CMP0048 state)
message("在 VERSION 2.8 下 CMP0048 状态: '${state}'")# -------------------------
# 切换到 3.0 的策略默认
cmake_policy(VERSION 3.0)cmake_policy(GET CMP0046 state)
message("在 VERSION 3.0 下 CMP0046 状态: '${state}'")cmake_policy(GET CMP0048 state)
message("在 VERSION 3.0 下 CMP0048 状态: '${state}'")
其实我们可以执行下面这个来一键搭建出这个目录结构
mkdir -p test && cat > test/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 2.8)
project(TwoPoliciesDemo)# 设置策略到 2.8
cmake_policy(VERSION 2.8)cmake_policy(GET CMP0046 state)
message("在 VERSION 2.8 下 CMP0046 状态: '${state}'")cmake_policy(GET CMP0048 state)
message("在 VERSION 2.8 下 CMP0048 状态: '${state}'")# -------------------------
# 切换到 3.0 的策略默认
cmake_policy(VERSION 3.0)cmake_policy(GET CMP0046 state)
message("在 VERSION 3.0 下 CMP0046 状态: '${state}'")cmake_policy(GET CMP0048 state)
message("在 VERSION 3.0 下 CMP0048 状态: '${state}'")
EOF
接下来我们就来构建一下项目
mkdir build && cd build && cmake ..
这很符合我们的预期吧!!!
4.5.使用示例4—— cmake_policy(PUSH)
和 cmake_policy(POP)
我给你写一个最小的 CMake 例子,演示 cmake_policy(PUSH)
和 cmake_policy(POP)
的作用。
它们的作用就是:
-
PUSH
:保存当前所有策略的状态(相当于“入栈”)。 -
POP
:恢复到上一次PUSH
时保存的状态(相当于“出栈”)。
这样你就可以在某个局部范围内改策略,出来之后恢复原来的状态,不影响全局。
项目结构
test/
└── CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(PolicyPushPopDemo)# 一开始获取 CMP0048 策略的状态
cmake_policy(GET CMP0048 state)
message("初始 CMP0048 状态: '${state}'")# 保存当前策略状态
cmake_policy(PUSH)# 在 PUSH 和 POP 之间,我们可以随便改策略
cmake_policy(SET CMP0048 NEW)
cmake_policy(GET CMP0048 state)
message("在 PUSH 之后修改 CMP0048 状态: '${state}'")# 恢复到 PUSH 时的状态
cmake_policy(POP)# 再次获取 CMP0048 状态,验证它已经恢复
cmake_policy(GET CMP0048 state)
message("POP 之后 CMP0048 状态: '${state}'")
其实我们可以执行下面这个来一键搭建出这个目录结构
mkdir -p test && cat > test/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 2.8)
project(PolicyPushPopDemo)# 一开始获取 CMP0048 策略的状态
cmake_policy(GET CMP0048 state)
message("初始 CMP0048 状态: '${state}'")# 保存当前策略状态
cmake_policy(PUSH)# 在 PUSH 和 POP 之间,我们可以随便改策略
cmake_policy(SET CMP0048 NEW)
cmake_policy(GET CMP0048 state)
message("在 PUSH 之后修改 CMP0048 状态: '${state}'")# 恢复到 PUSH 时的状态
cmake_policy(POP)# 再次获取 CMP0048 状态,验证它已经恢复
cmake_policy(GET CMP0048 state)
message("POP 之后 CMP0048 状态: '${state}'")
EOF
接下来我们就来构建一下项目
mkdir build && cd build && cmake ..
这很符合我们的预期吧!!!
-
PUSH = 入栈保存策略状态
-
POP = 出栈恢复之前的状态
-
演示的结果正好说明:中间即使修改过策略,在
POP
之后都会恢复原状。