策略

偶尔对 CMake 进行了较新的功能或更改,它与较旧版本不完全向后兼容。当有人尝试将旧 CMakeLists 文件与较新版本的 CMake 一起使用时,这可能会产生问题。为了帮助最终用户和开发人员解决此类问题,我们引入了 cmake-policies。策略是一种机制,可帮助提高向后兼容性并跟踪不同 CMake 版本之间的兼容性问题。

设计目标

CMake 策略机制的四个主要设计目标

  1. 现有项目应该在较新版本的 CMake 上构建,这些版本比项目作者使用的版本更新。

    • 用户不需要编辑代码来构建项目。

    • 可以发出警告,但应该构建项目。

  2. 兼容性要求不应抑制新接口的正确性或旧接口中的错误修复。最新接口的任何正确性降低对新项目都是不公平的。

  3. 必须记录对 CMake 做的可能需要更改项目的 CMakeLists 文件的每项更改。

    • 每个更改还应该具有唯一的标识符,可以用作警告和错误消息的参考。

    • 仅当项目某种程度上表明它受支持时才启用新的行为。

  4. 我们必须最终能够移除实现了与旧版 CMake 兼容的代码。

    • 这种移除对于保持代码的简洁性以及允许内部重构是必要的。

    • 在进行了此类移除后,针对旧版本的项目编写的构建尝试必须失败并附有提供信息的错误消息。

CMake 中的所有策略都被分配了一个名称,形式为 CMPNNNN,其中 NNNN 为整数值。策略一般支持两种行为:与较早版本 CMake 保持兼容性的旧行为,以及被认为正确且更适合新项目使用的行为。每个策略都有详细说明更改动机的文档,以及旧行为和新行为。

设置策略

项目可以配置每个策略的设置以请求旧行为或新行为。当 CMake 遇到可能受特定策略影响的用户代码时,它会检查项目是否设置了策略。如果已设置策略(为 OLDNEW),那么 CMake 会遵循指定的行为。如果未设置策略,则会使用旧行为,但会发出警告,告知项目作者设置策略。

有几种方法可以设置策略的行为。最快的方法是将所有策略设置为与项目的 CMake 发布版本对应的版本。设置策略版本会请求在 CMake 对应版本或更早版本中引入的所有策略的新行为。稍后版本中引入的策略被标记为“未设置”,以产生正确的警告消息。策略版本是使用 cmake_policy 命令的 VERSION 签名进行设置的。例如,代码

cmake_policy(VERSION 3.20)

会请求在 CMake 3.20 或更早版本中引入的所有策略的新行为。

cmake_minimum_required 命令将需要最低版本的 CMake 并且它将调用 cmake_policy。项目始终应该以以下行开头

cmake_minimum_required(VERSION 3.20)
project(MyProject)
# ...code using CMake 3.20 policies

这表明运行 CMake 的人必须至少具有版本 3.20。如果他们运行的是较早版本的 CMake,则会显示一条错误消息,告诉他们该项目至少需要指定版本的 CMake。

当然,您应该用您当前正在写入的 CMake 版本替换“3.20”。您也可以根据需要分别设置每个策略;对于希望逐步转换其项目以使用新行为或取消对旧行为的依赖警告的项目作者来说,这有时会很有帮助。 cmake_policy 命令的 SET 选项可用于明确请求某个策略的旧行为或新行为。

例如,CMake 2.6 引入了策略 CMP0002,它要求所有逻辑目标名称在全局中唯一(在某些情况下,重复目标名称以前会意外生效,但未得到诊断)。正在使用重复的目标名称且意外生效的项目会收到引用该策略的警告。可以通过代码取消这些警告

cmake_policy(SET CMP0002 OLD)

该代码明确地告诉 CMake 对策略使用旧行为(在不提示的情况下接受重复的目标名称)。另一个选择是使用代码

cmake_policy(SET CMP0002 NEW)

明确地告诉 CMake 使用新行为并在创建重复目标时产生错误。将此代码添加到项目后,它将不会构建,直到作者删除所有重复的目标名称。

当发布新的 CMake 版本时,它会引入可以在不更改任何旧策略的情况下继续构建旧项目的新策略,因为默认情况下,它们不会对任何新策略请求NEW行为。在启动一个新项目时,应该使用cmake_minimum_required指定 CMake 中支持的最新的版本。这将确保使用该版本的 CMake 中的策略来编写项目,并且不会使用任何旧的行为。如果未设置策略版本,CMake 将会发出警告并假定策略版本为 2.4。这允许未指定cmake_minimum_required的现有项目像使用 CMake 2.4 构建时一样进行构建。

策略栈

策略设置使用栈进行限定。在进入项目的子目录(使用add_subdirectory)时会压入一个新的栈级别,并在离开时弹出。因此,在项目的某个目录中设置策略不会影响父目录或同级目录,但会影响子目录。

当一个项目包含独立维护但在树内构建的子项目时,这很有用。项目中的顶级CMakeLists文件可能会写入

cmake_policy(VERSION 2.6)
project(MyProject)
add_subdirectory(OtherProject)
# ... code requiring new behavior as of CMake 2.6 ...

OtherProject/CMakeLists.txt文件包含

cmake_policy(VERSION 2.4)
projectS(OtherProject)
# ... code that builds with CMake 2.4 ...

这允许项目更新到 CMake 2.6,而子项目、模块和包含的文件在维护人员更新之前会继续使用 CMake 2.4 构建。

只要每个 push 都与 pop 配对,用户代码可以使用cmake_policy命令来 push 和 pop 自己的栈级别。当需要对代码的一小部分临时请求不同的行为时,这会很有用。例如,策略CMP0003在使用新行为时,会删除过去包含的多余链接目录。在增量更新项目时,使用剩余目标来构建特定目标可能很困难。代码

cmake_policy(PUSH)
cmake_policy(SET CMP0003 OLD) # use old-style link for now
add_executable(myexe ...)
cmake_policy(POP)

会取消警告,并对该目标使用旧行为。以下是如何以从命令行运行 CMake 来获取策略列表和特定策略的帮助

cmake --help-command cmake_policy
cmake --help-policies
cmake --help-policy CMP0003

更新项目以适用于 CMake 新版本

当 CMake 版本引入新策略时,可能会为某些现有项目生成警告。这些警告指示可能需要对项目进行更改来应对新策略。虽然项目的旧版本可以继续使用这些警告来构建,但应更新项目开发树来考虑新策略。更新树有两种方法:一次性和增量式。哪种方法更容易取决于项目的大小以及产生警告的新策略。

一次性方法

为项目更新新版本 CMake 最简单的方法是简单地更改在项目顶部设置的策略版本。然后尝试使用新版本 CMake 进行构建以解决问题。例如,要更新一个项目以使用 CMake 3.20 进行构建,可以编写

cmake_minimum_required(VERSION 3.20)

在顶层 CMakeLists 文件的开头。这告诉 CMake 对在 CMake 3.20 及更低版本中引入的每个策略使用新行为。使用 CMake 3.20 构建此项目时,不会产生关于策略的警告,因为它知道在更高版本中没有引入任何策略。但是,如果项目依赖于旧策略行为,则可能无法构建,因为 CMake 现在正在不发出警告的情况下使用新行为。这取决于添加策略版本行的项目作者来解决这些问题。

增量方法

为项目更新新版本 CMake 的另一种方法是逐一处理每个警告。这种方法的一个优点是项目将在整个过程中继续构建,因此可以逐步进行更改。

当 CMake 遇到来自身份是否对于策略使用旧行为还是新行为的情况时,它会检查项目是否已经设置了该策略。如果设置了策略,CMake 会在不发出提示的情况下使用相应的行为。如果策略未设置,CMake 会使用旧行为,但会警告作者尚未设置策略。

在许多情况下,警告消息都会指向CMakeLists 文件中导致警告的那行代码。在某些情况下,无法诊断出这种情况,直到 CMake 为项目生成本机构建系统规则,因此警告不会包含显式上下文信息。在这些情况下,CMake 会尝试提供有关可能需要更改代码位置的一些信息。这些“生成时间”策略的文档应指示项目代码中策略生效设置的位置。

为了逐步更新项目,应该一次解决一个警告。可能会出现几种情况,如下所述。

在代码正确时禁止警告

可能产生许多策略警告,仅仅是因为项目未设置策略,即使项目可能在新行为下正常工作(CMake 无法知道差异)。对于一些策略的警告,CMP<NNNN>,可以通过添加

cmake_policy(SET CMP<NNNN> NEW)

添加到项目的顶部并尝试构建它来检查是否是这种情况。如果项目在新行为下正确构建,则继续执行下一个策略警告。如果项目没有正确构建,则可能适用其他情况之一。

在不更新代码的情况下禁止警告

用户可以通过添加将一个警告的所有实例抑制为 CMP<NNNN>

cmake_policy(SET CMP<NNNN> OLD)

在项目顶部。但是,我们建议项目作者更新他们的代码以适应所有策略的新行为。这点尤为重要,因为将来(久远)的 CMake 版本可能会移除对旧行为的支持,并针对要求旧行为的项目生成错误(提示用户获取较低版本的 CMake 来构建项目)。

通过更新代码来消除警告

当一个项目无法正确使用某个策略的新行为时,就需要更新此代码。为了解决关于某个策略 CMP<NNNN> 的警告,请将添加

cmake_policy(SET CMP<NNNN> NEW)

在项目的顶部,然后修复此代码以便适应新行为。

如果出现大量警告,则同时修复所有警告可能十分困难:相反,开发人员可以通过使用 cmake_policy 命令的 PUSH/POP 函数签名一次修复一个警告

cmake_policy(PUSH)
cmake_policy(SET CMP<NNNN> NEW)
# ... code updated for new policy behavior ...
cmake_policy(POP)

这将针对经过修复的小部分代码请求新行为。该策略警告的其他实例可能仍然会出现,并且必须分别予以修复。

更新项目策略版本

在解决所有策略警告并且使用新的 CMake 版本干净地构建项目之后,仍然需要执行一个步骤。现在项目顶部分设的策略版本应该更新以匹配新的 CMake 版本,就像如上所述的一步解决办法中一样。例如,在使用 CMake 3.20 干净地构建项目之后,用户可以使用如下行更新项目的顶部

cmake_minimum_required(VERSION 3.20)

这将把在 CMake 3.20 或更低版本中引入的所有策略设为使用新行为。然后,用户可以扫过代码的其余部分,并移除使用 cmake_policy 命令来逐步请求新行为的调用。最后结果看起来应该与一步解决办法相同,但是可以逐步实现。

支持多个 CMake 版本

某些项目可能需要同时支持多个版本的 CMake。目标是使用较低版本进行构建,同时也在不发出警告的情况下使用较新版本进行构建。为了同时支持 CMake 2.4 和 2.6,可以编写类似的代码

cmake_minimum_required(VERSION 2.4)
if(COMMAND cmake_policy)
  # policy settings ...
  cmake_policy(SET CMP0003 NEW)
endif()

这将把策略设为使用 CMake 2.6 进行构建并且针对 CMake 2.4 忽略策略。为了同时支持 CMake 2.6 和 CMake 2.8 的若干策略,可以编写类似的代码

cmake_minimum_required(VERSION 2.6)
if(POLICY CMP1234)
  # policies not known to CMake 2.6 ...
  cmake_policy(SET CMP1234 NEW)
endif()

这将把策略设为使用 CMake 2.8 进行构建并且针对 CMake 2.6 忽略策略。如果此项目已知会同时使用 CMake 2.6 和 CMake 2.8 的新策略,则用户可以编写

cmake_minimum_required(VERSION 2.6)
if (NOT ${CMAKE_VERSION} VERSION_LESS 2.8)
   cmake_policy(VERSION 2.8)
endif()

检查 CMake 版本

CMake 是一款持续发展的程序,随着新版本的发布,会引入新的特性或命令。因此,您有时可能需要使用当前版本 CMake 中存在的命令,但之前版本中没有。有若干方法可以处理此类情况,一个方法是使用 if 命令检查新命令是否存在。例如

# test if the command exists
if(COMMAND some_new_command)
  # use the command
  some_new_command( ARGS...)
endif()

或者,可以通过评估 CMAKE_VERSION 变量来测试实际运行的 CMake 版本

# look for newer versions of CMake
if(${CMAKE_VERSION} VERSION_GREATER 3.20)
  # do something special here
endif()

最后,某些新版本的 CMake 可能不再支持您正在使用的某些行为(虽然我们努力避免这种情况)。在这种情况下,请使用 CMake 策略,如 cmake-policies 手册中所述。