【学习笔记】Mastering CMake (六)—— 策略

前言:

学习笔记,随时更新。如有谬误,欢迎指正。


说明:

  1. 红色字体为较为重要部分。
  2. 绿色字体为个人理解部分。

6 策略

有些时候 CMake 有会一个新的特性或者变更,但是并不完全与旧的 CMake 版本后向兼容。当有人在新版本的 CMake 上使用旧版的 CMakeLists 文件时就会产生问题。为了帮助终端用户和开发者解决这类问题,我们将介绍 cmake-policies策略是 CMake 的一个机制,它用来帮助提升后向兼容性,并且在不同的 CMake 版本之间跟踪兼容性问题

6.1 设计目的

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

  • 已存在的项目在构建时所使用的 CMake 版本应该比项目的作者所使用的更新。
    • 用户不应该需要去编辑代码(才能)让项目构建。
    • 可能会产生警告,但是项目应该(能够被)构建。
  • 新接口的正确性和旧接口的漏洞修复不应该被兼容性要求所限制。任何新接口的正确性的削减对于新项目来说都是不公平的。
  • 任何需要修改项目 CMakeLists 文件的 CMake 的改变不应该被记录。
    • 任何变更都要有一个标识符以便被警告或错误信息所引用。
    • 新的行为仅仅在项目已经以某种方式表明该行为被支持时才会起效。
  • 我们必须最终能够删除那些为了实现老版本 CMake 兼容性的代码。
    • 这些删除(操作)对保持代码整洁以及允许内部重构来说是很有必要的。
    • 在这些删除(操作)之后,试图构建以旧版本 CMake 写的项目时必定会失败,并且会有提示信息。

CMake 中,所有的策略会被指定以 CMPNNNN 的形式的名称, NNNN 是一个整型值。策略通常即支持保留了早期 CMake 版本兼容的旧行为,也支持被推荐在新项目中使用的正确的新行为。每一个策略都会有文档来详细说明这个修改的动机,以及新旧行为(的使用方式)。

6.2 设置策略

项目可以配置每一个策略的设置来获取旧的或新的行为。当 CMake 遇到了可能会被特殊策略所影响的用户代码,它将会去检测项目是否设置了该策略。如果设置了该策略(设置为 OLD 或者 NEW ), 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)
# ... 使用 CMake 3.20 策略的代码

这表明运行 CMake 的人必须至少有 3.20 版本。如果他们正在运行旧版本的 CMake ,将显示一个错误消息,告诉他们该项目至少需要指定版本的 CMake 。

当然,“3.20”能被替换成任何你正在写的(项目所使用的) CMake 版本。如果你想的话你也可以单独地设置每一个策略。当作者想要增量地转换他们的项目来使用新行为,或者屏蔽旧行为的依赖的警告的时候这会很有用。 cmake_policy 命令的 SET 选项可以被用来显式地对特定的策略设置使用旧或者新行为

例如, CMake 2.6 引入了 CMP0002 策略,它要求所有逻辑目标的名称必须全局唯一(重复的目标名称以前某些情况下会偶尔工作正常,但不是确定的)。项目中使用重复的目标名称并且在偶尔能工作正常时会收到引用该策略的警告。该警告可以通过下面代码屏蔽:

cmake_policy(SET CMP0002 OLD)

它将会告诉 CMake 使用该策略的旧行为(静默地接受重复的目标名称)。另一个选项时使用下面的代码:

cmake_policy(SET CMP0002 NEW)

它会显式地告诉 CMake 使用新行为并且在创建一个名称重复的目标时产生一个错误。一旦它被加入到项目中,直到作者移除所有重复的目标名称之后项目才能构建。

当 CMake 的新版本发布并引入新策略时,它仍然能构建旧的项目,因为默认情况下它们不会为任何新策略请求新的行为。当启动一个新项目时,应该始终通过 cmake_minimum_required 命令来指定支持的 CMake 的最新版本。这将确保项目是使用该版本的 CMake 策略编写的,而不是使用任何旧的行为。如果没有设置策略版本, CMake 会发出警告,并推测(使用) 2.4 版本的策略。这就使得那些没有指定 cmake_minimum_required 的项目能够构建了,因为它们可能有 CMake 2.4 。

6.3 策略栈

策略设置使用堆栈限定范围。当进入项目的一个新的子目录(使用 add_subdirectory )的时候新级别的栈被推入,离开的时候被弹出。因此在项目的一个目录中设置一个策略将不会影响到父或者兄弟目录,但是会影响子目录

当项目中包含需要单独维护但却在项目树中构建的子项目时这就非常有用了。项目中顶层的 CMakeLists 文件可能这么写:

cmake_policy(VERSION 2.6)
project(MyProject)
add_subdirectory(OtherProject)
# ... 代码需要 CMake 2.6 中新的行为 ...

而 OtherPorject/CMakeLists.txt 包含如下内容:

cmake_policy(VERSION 2.4)
projectS(OtherProject)
# ... 代码使用 CMake 2.4 构建 ...

这允许一个项目更新到 CMake 2.6 ,但是子项目、模块和包含文件仍然使用 CMake 2.4 来构建,直到它们的维护者更新他们。

用户的代码中会使用 cmake_policy 命令来推入和弹出它们自己的(策略)栈,并且推入和弹出是成对出现的。这对为一小段代码临时地设置不同的行为是很有用的。例如,当使用新行为时, CMP0003 策略会移除被用来包含的额外的链接路径。由于增量地更新了一个项目,可能很难在其他目标正常的情况下建立一个特定的目标。

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

这个代码将会屏蔽警告并且对那个目标( myexe )使用旧行为。你可以通过命令行来如下运行 CMake 来获取策略列表以及特定策略的帮助(信息):

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

6.4 为项目更新新的 CMake 版本

当一个 CMake 发布版本引入了新的策略,一些现存的项目可能会产生一些警告。这些警告表明需要对项目做一些改变来正确地处理这些新策略。虽然项目的旧版本可以在有警告的情况下继续构建的时候,但是项目的开发树也应该被更新来把新的策略考虑进来。有两个方式来更新一个树:一次性的增量的。哪一个比较容易取决于项目的体量以及哪些新策略会产生警告。

6.4.1 一次性的方式

更新项目来使用一个新的 CMake 版本的最简单的方式就是改变在项目的开头策略版本设置。然后尝试用新的 CMake 版本来构建,然后解决(因策略变更导致的警告)问题。例如,更新一个项目来用 CMake 3.20 来构建,可能会在顶层的 CMakeLists 文件的开头这么写:

cmake_minimum_required(VERSION 3.20)

这就告诉 CMake 使用 CMake 3.20 及以下版本中所引入的策略的新行为。当项目使用 CMake 3.20 来构建时将不会产生关于策略的警告,因为它并不知道后续版本所引入的策略。然而,如果项目依赖一个策略的旧行为,那么在 CMake 现在使用新行为而没有警告的时候项目才能构建。这需要添加策略版本这一行的项目作者来解决这些问题。

6.4.2 增量的方式

另一个更新项目来使用新的 CMake 版本的方式就是一个一个的解决每一个警告。这种方式的一个优势是项目将在整个过程中持续不断地构建,因此可以增量地进行更改。

当 CMake 遇到需要知道是使用一个策略的旧行为还是新行为的场景时,它将会检查这个项目是否设置了策略。如果设置了策略, CMake 会默默地执行对应的行为。如果没设置策略, CMake 使用旧行为,但是会发出该策略未设置的警告。

在许多情况下,警告信息将会指出产生警告的 CMakeLists 文件的代码的确切的行数。某些情况下,直到 CMake 为项目生成原生构建系统规则之后问题才能被诊断出来,因此警告将不会包含明确的上下文信息。在这些情况下, CMake 将会尝试提供一些关于哪里的代码需要被更改的信息。这些“生成时期”策略的文档应该指出在项目代码的什么位置设置该策略才会生效。

为了逐步更新一个项目,必须每次只提出一个警告。可能会发生如下所述的几种情况。

6.4.3 屏蔽代码正确时的警告

许多策略警告会很容易产生,因为即使项目在新行为下也能正常工作,但项目并没有设置该策略(此时也会产生警告, CMake 无法知道区别)。对于一些关于 CMP<NNNN> 策略的警告,可以通过在项目顶部添加如下代码并尝试构建它来检查情况是否如此:

cmake_policy(SET CMP<NNNN> NEW)

如果项目可以在新行为下正确地构建,你旧可以去处理下一个策略警告了。如果项目无法正确地构建,可能需要应用另一种情况。

6.4.4 不更新代码的情况下屏蔽警告

你可以通过在项目的顶部添加如下代码来消除 CMP<NNNN> 警告的所有实例:

cmake_policy(SET CMP<NNNN> OLD)

然而,我们鼓励项目的作者更新代码来在所有策略的新行为下工作。这是尤为重要的,因为在(遥远的)将来的 CMake 版本可能会删除对旧行为的支持,并且对使用它的项目生成一个错误(这会导致用户会去使用一个更旧的 CMake 来构建项目)。

6.4.5 更新代码来屏蔽警告

当一个项目在一个策略的新行为下不能正常工作,那就需要个更新代码。为了解决某些 CMP<NNNN> 策略的警告,你可能需要在项目的开头添加如下代码,并调整代码以让其在新行为下正常工作:

cmake_policy(SET CMP<NNNN> NEW)

如果(一次性)出现了多条警告,同时全部修复他们可能比较困难。相比而言,可以通过使用 cmake_policy 命令的 PUSH/POP 标记来让开发者一次只修复一个:

cmake_policy(PUSH)
cmake_policy(SET CMP<NNNN> NEW)
# ... 为新策略行为更新了代码 ...
cmake_policy(POP)

这将会在被修复的一小部分代码中使用新行为。其他的策略警告可能仍然存在,并且必须被分开修复。

6.4.6 更新项目的策略版本

在处理了所有的策略警告并且项目能够在新的 CMake 版本下干净利落地构建后,还有一个步骤(要处理)。被设置在项目头部的策略版本现在需要被更新来匹配新的 CMake 版本,就像上面说的一次性的方式一样。例如,更新项目后,(项目)可以使用 CMake 3.20 来干净利落地构建,用户可能会如下更新在项目头部的代码:

cmake_minimum_required(VERSION 3.20)

这将会将所有在 CMake 3.20 及以前版本的引入的策略都设置为使用新行为。然后你会扫描剩下的代码并且移除被用来增量地使用新行为的 cmake_policy 命令的调用。最终的结果看起来就像上面所说的一次性的方式,但是(最终的结果)是通过一步一步地获得的。

6.4.7 支持 CMake 的多个版本

一些项目可能想同时支持几个 CMake 版本。它的目标是能够使用旧版本构建,但也能没有警告地在新版本下工作。为了支持 CMake 2.4 和 2.6 ,你可能会写如下代码:

cmake_minimum_required(VERSION 2.4)
if(COMMAND cmake_policy)
    # 策略设置 ...
    cmake_policy(SET CMP0003 NEW)
endif()

这将会在用 CMake 2.6 构建时设置这些策略,而在用 CMake 2.4 时忽略它们(这样设置能生效是因为 CMake 2.6 才引入的 cmake_policy 命令)。为了支持 CMake 2.6 和一部分 CMake 2.8 的策略,你可能会写如下代码:

cmake_minimum_required(VERSION 2.6)
if(POLICY CMP1234)
    # CMake 2.6 不知道的策略 ...
    cmake_policy(SET CMP1234 NEW)
endif()

这将会在用 CMake 2.8 构建时设置这些策略,而在 CMake 2.6 是忽略它们(这里的 CMP1234 并不是真正的代号为 1234 的策略,只是举例。这里想表达的意思是如果定义了这个策略,就使用这个策略的新行为)。如果知道项目构建可以使用 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()
6.4.7.1 检查 CMake 版本

CMake 是一个不断发展的程序,随着新版本的发布,新的功能或命令会被引入。因此,在某些情况下,你可能想要使用当前版本的CMake 中有而以前版本没有的命令。有几种方法可以处理这个问题。一种选择是使用 if 命令检查是否存在新命令。例如:

# 检查命令是否存在
if(COMMAND some_new_command)
    # 使用命令
    some_new_command( ARGS...)
endif()

或者,可以通过计算 CMAKE_VERSION 变量来测试正在运行的 CMake 的实际版本

# 寻找更新版本的 CMake 
if(${CMAKE_VERSION} VERSION_GREATER 3.20)
    # 在这里做一些特殊的事情
endif()

最后,一些新版本的 CMake 可能不再支持你正在使用的某些行为(尽管 CMake 试图避免这种情况)。在这些情况下,使用 CMake 策略,如 cmake-policies 手册中所述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值