CMake(十一):策略

CMake已经发展了很长一段时间,引入了新功能,修复了bug,改变了某些特性的行为,以解决缺陷或引入改进。虽然新功能的引入不太可能给用CMake构建的现有项目带来问题,但如果项目依赖于旧的行为,任何行为上的改变都有可能破坏项目。出于这个原因,CMake开发人员小心地确保以这样一种方式实现更改,以保持向后兼容性,并为更新到新行为的项目提供一个直接的、受控的迁移路径。这种控制应该使用旧的还是新的行为是通过CMake的策略机制完成的。一般来说,策略并不是开发人员经常接触到的东西,主要是当CMake发出关于依赖于旧版本行为的项目的警告时。当开发人员转向最近的CMake版本时,新的CMake版本有时会发出这样的警告,以突出显示项目应该如何更新以使用新的行为。

11.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)。在最小和最大版本之间使用三个点指定范围,不包含空格。range表示使用的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文档提供了完整的策略列表,并解释了每种策略的OLD和NEW行为。

​ 举个例子,在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中的值将是OLD、NEW或empty。cmake_minimum_required(VERSION…)和cmake_policy(VERSION…)命令用来重置所有策略的状态。那些在指定的CMake版本或更早版本引入的策略被重置为NEW。在指定版本之后添加的策略将被有效地重置为空。

​ 如果CMake检测到项目正在做一些依赖于旧行为、与新行为冲突或行为不明确的事情,它可能会在相关策略未设置时发出警告。这些警告是开发人员暴露在CMake策略功能面前最常见的方式。它们被设计成嘈杂但信息丰富的,鼓励开发人员更新项目到新的行为。在某些情况下,即使策略已显式设置,也可能会发出弃用警告,但这通常只适用于已经被记录为弃用了很长一段时间(许多版本)的策略。

​ 有时,策略警告不能立即得到解决,但这些警告可能是不可取的。处理此问题的首选方法是显式地将策略设置为所需的行为(OLD或NEW),这将停止警告。但这并不总是可能的,比如当项目的更深层次的部分发出自己对cmake_minimum_required(VERSION…)或cmake_policy(VERSION…)的调用时,从而重置策略状态。作为解决这种情况的临时方法,CMake提供了CMAKE_POLICY_DEFAULT_CMPxxxx和CMAKE_POLICY_WARNING_CMPxxxx变量,其中xxxx是通常的四位策略号。这些并不打算由项目设置,而是由开发人员作为缓存变量临时启用/禁用警告,或者检查项目是否启用了特定策略发出警告。最终,长期的解决方案是解决警告所强调的根本问题。然而,对于一个项目来说,偶尔设置这些变量之一来消除已知无害的警告可能是合适的。

11.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选项,该选项阻止策略堆栈的自动推送弹出(add_subdirectory()没有这样的选项)。在CMake的早期版本中,include()和find_package()不会自动在策略堆栈上推送和弹出一个条目。添加NO_POLICY_SCOPE选项是为了在以后的CMake版本中使用项目的特定部分恢复到旧的行为,但是通常不鼓励使用它,并且对于新项目应该是不必要的。
相关代码:https://gitee.com/jiangli01/cmake-learning
更多请关注微信公众号【Hope Hut】:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值