> For the complete documentation index, see [llms.txt](https://chenxiaowei.gitbook.io/professional-cmake/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://chenxiaowei.gitbook.io/professional-cmake/part1-chinese/12-chinese.md).

# 第12章：策略

CMake已经发展了很长一段时间，引入了很多新的功能，修复了不少bug，并改变了某些特性的行为以解决缺陷或引入改进。虽然新功能的引入不太可能给现有项目的构建带来问题，但如果依赖于老式行为，任何改变都有可能破坏项目。由于这个原因，CMake的开发人员小心地确保实现更改能够向后兼容，并为更新到新行为的项目提供一个直接的、可控的迁移方式。

对新旧行为的控制可以通过CMake的策略机制完成。策略并不是开发者经常接触到的东西，当开发人员使用了另一个CMake版本，新的CMake版本有时会发布这样的警告，强调这个项目应该如何更新/使用新式的行为。

## 12.1. 政策控制

CMake的策略功能与cmake\_minimum\_required()命令紧密相连，该命令在第3章中介绍过。该命令不仅指定了项目所需的最低CMake版本，还将CMake的行为设置为与给定的版本相匹配。因此，当项目以cmake\_minimum\_required(VERSION 3.2)开始时，就表明至少需要CMake 3.2版本，并且该项目希望当前CMake的行为和3.2版本一样。开发者可以在任何时间点对CMake进行替换(向高版本)，并且项目仍然会像以前一样构建。

但是，有时项目需要比cmake\_minimum\_required()命令更细粒度的控制。考虑以下场景:

* 项目设置了一个较低的CMake版本，但如果可能，也希望使用新式行为。
* 项目的一部分是无法修改(例如：它可能来自一个外部代码库)，它依赖的旧式行为在CMake的新版本中已经修改了。不过，项目的其余部分希望采用新式行为。
* 项目严重依赖于一些旧式行为，这些行为需要大量的成本进行更新。项目的一些部分想要利用最近的CMake特性，但是对于特定的更改，旧式行为需要保留，直到有时间来更新项目为止。

这些的示例中，仅cmake\_minimum\_required()命令提供的控制是不够的。可以通过cmake\_policy()命令对策略进行更具体的控制，该命令有许多不同粒度的方式。在最粗粒度的方式与cmake\_minimum\_required()的效果非常接近:

```
cmake_policy(VERSION major[.minor[.patch[.tweak]]])
```

在这种方式中，命令更改了CMake的行为以匹配指定版本的行为，cmake\_minimum\_required()命令会隐式设置CMake的行为。除了项目顶部强制调用cmake\_minimum\_required()来强制最低CMake版本外，这两种方式在很大程度上是可互换的。除了顶级CMakeLists.txt文件的开始部分，使用cmake\_policy()通常可以更清楚地传达意图，当项目需要对项目的某个部分执行特定版本的行为时，就如下面的例子所示:

```
cmake_minimum_required(VERSION 3.7)
project(WithLegacy)

# Uses recent CMake features
add_subdirectory(modernDir)

# Imported from another project, relies on old behavior
cmake_policy(VERSION 2.8.11)
add_subdirectory(legacyDir)
```

CMake 3.12(以向后兼容的方式)扩展了这个功能，允许项目指定一个版本范围，而不是一个版本，可以指定cmake\_minimum\_required()或cmake\_policy(VERSION)。范围是用三个点…在最低和最高版本之间，没有空格。范围表示正在使用的CMake版本必须至少是最小值，要使用的行为应该是指定的最大值和正在运行的CMake版本的最小值。这相当于项目表示，“我至少需要CMake X，但我可以安全地使用CMake Y的策略”。下面的例子展示了一个项目只需要CMake 3.7的两种方法，但是仍然支持CMake 3.12之前所有策略的新式行为(如果运行的CMake版本支持的话):

```
cmake_minimum_required(VERSION 3.7...3.12)
cmake_policy(VERSION 3.7...3.12)
```

3.12之前的CMake版本实际上只看到一个版本号，并忽略…3.12部分，而3.12之后的版本可以理解为一个范围。

CMake还提供了能力集，可以单独的控制每个行为变化与SET的形式:

```
cmake_policy(SET CMPxxxx NEW)
cmake_policy(SET CMPxxxx OLD)
```

每个单独的行为更改都有自己的策略号CMPxxxx，其中xxxx总是四位数。通过指定NEW或OLD，项目告诉CMake为特定策略使用NEW或OLD行为。CMake文档提供了策略的完整列表，并解释了每种策略的新旧行为。

作为一个例子，3.0版本之前CMake允许项目将get\_target\_property()与不存在名称目标的一起使用。这种情况下，属性的值被返回为NOTFOUND，而不是直接报错，但项目很可能包含不正确的逻辑。因此，从3.0版本开始，如果遇到这样的情况，CMake会在出现该错误时停止。如果一个项目依赖于旧式行为，它可以继续使用策略CMP0045，像这样:

```
# Allow non-existent target with get_target_property()
cmake_policy(SET CMP0045 OLD)

# Would halt with an error without the above policy change
get_target_property(outVar doesNotExist COMPILE_DEFINITIONS)
```

为NEW操作制定策略的需求就不那么常见了。一种情况是，项目想要设置一个较低的CMake版本，但如果使用的是较新的版本，仍然可以利用后续的特性。例如，在CMake 3.2中引入了策略CMP0055，提供对break()命令使用情况的检查。如果项目仍然想要支持在早期CMake版本中构建，在运行新的CMake版本时必须显式启用检查。

```
cmake_minimum_required(VERSION 3.0)
project(PolicyExample)

if(CMAKE_VERSION VERSION_GREATER 3.1)
 # Enable stronger checking of break() command usage
 cmake_policy(SET CMP0055 NEW)
endif()
```

测试CMAKE\_VERSION变量是确定策略是否可用的一种方法，而if()命令提供了一种更直接的方法，直接使用if(POLICY…)形式。上述方式也可以这样执行:

```
cmake_minimum_required(VERSION 3.0)
project(PolicyExample)

# Only set the policy if the version of CMake being used
# knows about that policy number
if(POLICY CMP0055)
 cmake_policy(SET CMP0055 NEW)
endif()
```

还可以获得特定策略的当前状态。当前策略设置可能在模块文件中，它可能是由CMake本身或项目提供。然而，项目模块根据策略设置改变其行为是不常见的。

```
cmake_policy(GET CMPxxxx outVar)
```

存储在outVar中的值将是旧的、新的或空的。cmake\_minimum\_required(VERSION…)和cmake\_policy(VERSION…)命令重置所有策略的状态。那些在指定CMake版本或更早版本引入的策略将重置为NEW。在指定版本之后添加的策略将重置为空。

如果CMake检测到项目正在依赖一些于旧式行为、并与新式行为有冲突，或其行为含糊不清时，在相关策略未设置时会发出警告。这些警告是向开发人员展示CMake策略的最常见方式。它们的设计为噪音大且信息量大，其鼓励开发商更新项目以适应新的行为。某些情况下，即使策略已明确设置，也可能会发出弃用警告，但这通常只针对已弃用很长时间(许多版本)的策略。

有时不能立即处理策略警告，这些警告可能也不需要处理。处理此问题的首选方法是显式地将策略设置为所需的行为(旧式或新式)，这将关闭警告。但这中办法并不总是可行，比如项目的较深的部分调用了cmake\_minimum\_required(VERSION…)或cmake\_policy(VERSION…)，重新设置策略状态时。作为解决这种情况的临时方法，CMake提供了CMAKE\_POLICY\_DEFAULT\_CMPxxxx和CMAKE\_POLICY\_WARNING\_CMPxxxx变量，其中xxxx是通常的四位数策略号。这些不由项目设置，而是由开发人员临时作为缓存变量来启用/禁用警告，或者检查项目是否在启用特定策略时发出警告。最终，长期解决方案是解决上述警告所突显的根本问题。然而，对于项目来说，设置这些变量中的一个来屏蔽已知无害的警告是合适的。

## 12.2. 策略范围

有时策略设置只需要应用于文件的特定部分，所以CMake提供了一个策略堆栈，可以用来简化这个过程，而不需要手动保存任何想临时改变策略的现有值：

```
cmake_policy(PUSH)
cmake_policy(POP)
```

所有策略的现有状态可以通过PUSH操作保存，并通过相应的POP丢弃当前状态。每次PUSH都需要最终需要一个匹配的POP。这两者之间，项目可以修改它需要的任何策略的设置，而不必先显式地保存每个策略。同样，模块文件是策略堆栈操作的常见位置之一。为一个模块文件制定一些暂时策略:

```
# Save existing policy state
cmake_policy(PUSH)

# Set some policies to OLD to preserve a few old behaviors
cmake_policy(SET CMP0060 OLD) # Library path linking behavior
cmake_policy(SET CMP0021 OLD) # Tolerate relative INCLUDE_DIRECTORIES

# Do various processing here...

# Restore earlier policy settings
cmake_policy(POP)
```

有些命令隐式地将一个新的策略状态推送到栈上，在稍后某个定义的点再次弹出它。一个例子是add\_subdirectory()命令，它在进入指定的子目录时将新的策略范围压入堆栈，并在命令返回时弹出它。include()命令执行类似的操作，在开始处理指定的文件之前推入一个新的策略范围，并在处理完该文件后再次弹出该文件。find\_package()命令也对include()执行类似的操作，即在启动和完成与之相关的FindXXX.cmake模块文件的处理时，分别进行压入和弹出。

include()和find\_package()命令还支持NO\_POLICY\_SCOPE选项，该选项可以防止策略栈的自动push-pop(add\_subdirectory()没有这样的选项)。在CMake的早期版本中，include()和find\_package()不会自动在策略栈上推入和弹出。添加NO\_POLICY\_SCOPE选项是为了让以后使用CMake版本的项目在特定部分恢复到旧的行为，但是通常不鼓励这样用，而且对于新项目也没必要。

## 12.3. 推荐

在可能的情况下，项目应该更喜欢使用CMake版本级别的策略，而不是操作特定的策略。设置策略来匹配特定的CMake版本的行为，会使项目更容易理解和更新，而对单个策略的更改可能很难通过多个目录级别的跟踪，特别是与版本级策略更改的交叉使用，而版本级策略更改总是会重置。

选择如何指定要遵循的CMake版本时，cmake\_minimum\_required(VERSION)和cmake\_policy(VERSION)之间通常会选择后者。这方面的第一个例外是在项目的顶级CMakeLists.txt文件的开始，以及可以跨多个项目重用的模块文件的开始部分。对于第二个例外，最好使用cmake\_minimum\_required(VERSION)，因为使用该模块的项目可能会强制执行它们自己的最低CMake版本，但是该模块可能有自己特定的最小版本需求。除了这些情况，cmake\_policy(VERSION)通常能更清楚地表达了意图，但从策略的角度来看，这两个命令都能有效地实现相同的目的。

在项目确实需要操作特定策略的情况下，应该使用if(policy…)检查策略是否可用，而不是测试CMAKE\_VERSION变量。比较以下两种设置策略行为的方法，并注意检查和执行如何使用策略一致的方法:

```
# Version-level policy enforcement
if(NOT CMAKE_VERSION VERSION_LESS 3.4)
 cmake_policy(VERSION 3.4)
endif()

# Individual policy-level enforcement
if(POLICY CMP0055)
 cmake_policy(SET CMP0055 NEW)
endif()
```

如果项目需要在本地操作多个单独的策略，那么应该使用cmake\_policy(PUSH)和cmake\_policy(POP)的包围该部分，以确保与作用域的其余部分隔离。特别注意任何可能退出该代码段的return()语句，并确保留下相应pop的push。还要注意的是，add\_subdirectory()、include()和find\_package()都自动在策略堆栈上推入和弹出策略，所以不需要显式推入和弹出，除非需要为拉入的文件的一小部分在本地更改策略设置。项目应该避免使用这些命令中的NO\_POLICY\_SCOPE关键字，因为它仅用于处理早期CMake版本的行为变化，已经很少使用于新项目了。

为了避免修改函数内的策略设置。函数没有引入新的策略范围，如果没有使用适当的push-pop正确地隔离更改，则策略更改可能会影响调用方。此外，函数实现的策略设置来自函数的范围，而不是调用函数的范围。因此，最好在定义函数的范围内调整策略设置，而不是在函数本身内。

作为最后的部分，允许开发人员或项目为解决一些具体政策的情况，使用CMAKE\_POLICY\_DEFAULT\_CMPxxxx CMAKE\_POLICY\_WARNING\_CMPxxxx变量。开发人员可以使用它们临时更改特定策略设置的默认值，或者防止有关特定策略的警告。项目应该避免设置这些变量，以便开发人员能够在本地进行控制，但在某些情况下，即使调用cmake\_minimum\_required()或cmake\_policy(VERSION)，也可以使用它们来确保特定策略的行为或警告持续存在。不过，在可能的情况下，项目应该尝试更新到新式行为，而非依赖于这些变量。
