CMake(九):生成器表达式

当运行CMake时,开发人员倾向于认为它是一个简单的步骤,需要读取项目的CMakeLists.txt文件,并生成相关的特定于生成器的项目文件集(例如Visual Studio解决方案和项目文件,Xcode项目,Unix Makefiles或Ninja输入文件)。然而,这涉及两个截然不同的步骤。当运行CMake时,输出日志的末尾通常看起来像这样:

-- Configuring done
-- Generating done
-- Build files have been written to: /some/path/build

​ 当CMake被调用时,它首先读取并处理源树顶部的CMakeLists.txt文件,包括它拉进来的任何其他文件。在执行命令、函数等时,创建项目的内部表示。这称为配置步骤。控制台日志的大部分输出都是在此阶段生成的,包括message()命令的任何内容。在configure步骤的末尾,–configure done消息被打印到日志中。

​ 一旦CMake完成了读取和处理CMakeLists.txt文件,它就会执行生成步骤。这是使用在配置步骤中构建的内部表示创建构建工具的项目文件的地方。在大多数情况下,开发人员倾向于忽略生成步骤,只是将其视为配置的最终结果。控制台日志几乎总是在配置步骤完成后立即显示–Generating done消息,所以这是可以理解的。但在某些情况下,将其分为两个不同的阶段尤为重要。

​ 考虑一个项目处理一个多配置的CMake生成器,如Xcode或Visual Studio。当读取CMakeLists.txt文件时,CMake并不知道要为哪个配置构建目标。这是一个多配置的设置,所以有不止一个选择(例如调试,发布,等等)。开发人员在构建时选择配置,在CMake完成之后。如果CMakeLists.txt文件想要做一些事情,比如将文件复制到与给定目标的最终可执行文件相同的目录中,这似乎会出现一个问题,因为该目录的位置取决于正在构建的配置。需要一些占位符来告诉CMake“对于正在构建的任何配置,使用最终可执行文件的目录”。

​ 这是生成器表达式提供的功能的一个主要示例。它们提供了一种对某些逻辑进行编码的方法,这些逻辑在配置时不会计算,而是推迟到项目文件被写入时的生成阶段。它们可以用来执行条件逻辑,输出字符串,提供关于构建的各个方面的信息,如目录、名称、平台细节等。它们甚至可以用于根据正在执行的构建或安装提供不同的内容。

9.1 简单的布尔逻辑

​ 生成器表达式不能在任何地方使用,但是在很多地方都支持它们。在CMake参考文档中,如果一个特定的命令或属性支持生成器表达式,文档中会提到它。随着时间的推移,支持生成器表达式的属性集已经扩展,一些CMake版本也扩展了支持的表达式集。项目应该确认,对于他们所需要的最小CMake版本,被修改的属性确实支持所使用的生成器表达式。

​ 使用语法$<…>指定一个生成器表达式,其中尖括号之间的内容可以采用几种不同的形式。很快就会清楚,一个基本特征是有条件地包含内容。下面是最基本的生成器表达式:

$<1:...>
$<0:...>
$<BOOL:...>

​ 对于$<1:…>,表达式的结果将是…部分,而对于$<0:…>,…部分将被忽略,表达式将产生一个空字符串。 $<BOOL:…>表达式可以用来将任何被CMake识别为布尔型假值的值转换为0,其他值转换为1。这些生成器表达式一起提供了一种简单而强大的方法来选择性地包含内容。还支持逻辑操作:

$<AND:expr[,expr...]>
$<OR:expr[,expr...]>
$<NOT:expr>

​ 每个expr的值预期为1或0。AND和OR表达式可以接受任意数量的逗号分隔参数,并提供相应的逻辑结果,而NOT只接受一个表达式,并将产生其参数的否定。因为AND、OR和NOT要求它们的表达式的值只能为0或1,所以考虑将这些表达式封装在$中,以强制对被认为是true或false的表达式进行更宽容的逻辑处理。

​ 在CMake 3.8及以后的版本中,IF -then-else逻辑也可以非常方便地用一个专用的$表达式来表达:

$<IF:expr,val1,val0>

​ 通常,expr的值必须为1或0。如果expr的值为1,则结果为val1;如果expr的值为0,则结果为val0。在CMake 3.8之前,等价的逻辑必须以以下更冗长的方式表示,需要给出两次表达式:

$<expr:val1>$<$<NOT:expr>:val0>

​ 生成器表达式可以嵌套,允许构造任意复杂度的表达式。上面的例子显示了一个嵌套的条件,但是生成器表达式的任何部分都可以嵌套。下面的例子演示了到目前为止所讨论的特性:

ExpressionResultNotes
$<1:foo>foo
$<0:foo>
$<true:foo>Error, not a 1 or 0
$<$<​BOOL:true>:foo>foo
$<$<​NOT:0>:foo>foo
$<$<NOT:1>:foo>
$<$<NOT:tree>:foo>fooError, NOT requires a 1 or 0
$<$<AND:1,0>:foo>
$<$<OR:1,0>:foo>foo
$<1:$<$<BOOL:false>:foo>>
$<IF:$<BOOL:${foo}>,yes,no>Result will be yes or no depending on ${foo}

​ 就像if()命令一样,CMake也支持在生成器表达式中测试字符串、数字和版本,尽管语法略有不同。如果满足各自的条件,下列所有项的值都为1,否则为0。

$<STREQUAL:string1,string2>
$<EQUAL:number1,number2>
$<VERSION_EQUAL:version1,version2>
$<VERSION_GREATER:version1,version2>
$<VERSION_LESS:version1,version2>

​ 另一个非常有用的条件表达式是测试构建类型:

$<CONFIG:arg>

​ 如果arg对应于实际正在构建的构建类型,那么它的值将为1,对于所有其他构建类型,它的值将为0。它的常用用途是仅为调试构建提供编译器标志,或者为不同的构建类型选择不同的实现。例如:

add_executable(myApp src1.cpp src2.cpp)
# Before CMake 3.8
target_link_libraries(myApp PRIVATE
  $<$<CONFIG:Debug>:checkedAlgo>
  $<$<NOT:$<CONFIG:Debug>>:fastAlgo>
)
# CMake 3.8 or later allows a more concise form
target_link_libraries(myApp PRIVATE
  $<IF:$<CONFIG:Debug>,checkedAlgo,fastAlgo>
)

​ 上面会将可执行文件链接到用于调试构建的checkedAlgo库,以及用于所有其他构建类型的fastAlgo库。$<CONFIG:…> 生成器表达式是健壮地提供这种功能的唯一方法,它适用于所有的CMake项目生成器,包括像Xcode或Visual Studio这样的多配置生成器。

​ CMake提供了更多的基于平台和编译器细节、CMake策略设置等的条件测试。开发人员应该查阅CMake参考文档,了解支持的条件表达式的完整集合。

9.2 目标的细节

​ 生成器表达式的另一个常见用途是提供关于目标的信息。目标的任何属性都可以通过以下两种形式之一获得:

$<TARGET_PROPERTY:target,property>
$<TARGET_PROPERTY:property>

​ 第一个表单提供来自指定目标的命名属性的值,而第二个表单将从使用生成器表达式的目标检索属性。

​ 虽然TARGET_PROPERTY是一种非常灵活的表达式类型,但它并不总是获取目标信息的最佳方式。例如,CMake还提供了其他表达式,它们提供了关于目标构建的二进制文件的目录和文件名的详细信息。这些更直接的表达式负责提取某些属性的部分或基于原始属性计算值。其中最常用的是TARGET_FILE生成器表达式集:

  • TARGET_FILE

    这将生成目标二进制文件的绝对路径和文件名,包括任何与平台相关的文件前缀和后缀(例如.exe, .dylib)。对于基于unix的平台,其中共享库的文件名中通常包含版本细节,这些也将包括在内。

  • TARGET_FILE_NAME

    与TARGET_FILE相同,但没有路径(也就是说,它只提供文件名部分)。

  • TARGET_FILE_DIR

    与TARGET_FILE相同,但没有文件名。这是获取构建最终可执行文件或库所在目录的最健壮的方式。当使用像Xcode或Visual Studio这样的多配置生成器时,它的价值对于不同的构建配置是不同的。

​ 上面的三个TARGET_FILE表达式在定义自定义构建规则以在构建后的步骤中复制文件时特别有用。除了TARGET_FILE表达式外,CMake还提供了一些特定于库的表达式,它们具有类似的作用,只是它们处理文件名前缀和/或后缀细节的方式略有不同。这些表达式的名称以TARGET_LINKER_FILE和TARGET_SONAME_FILE开头,通常不像TARGET_FILE表达式那样频繁使用。

​ 支持Windows平台的项目还可以获取关于给定目标的PDB文件的详细信息。同样,这些都可以在定制构建任务中使用。以TARGET_PDB_FILE开头的表达式遵循与TARGET_PROPERTY类似的模式,提供用于使用生成器表达式的目标的PDB文件的路径和文件名详细信息。

​ 另一个与目标相关的生成器表达式值得特别提及。CMake允许一个库目标被定义为一个对象库,这意味着它不是一个通常意义上的库,它只是一个对象文件的集合,CMake与目标关联,但实际上并不会创建一个最终的库文件。因为它们是目标文件,所以不能作为一个单元链接(尽管CMake 3.12放宽了这个限制)。相反,它们必须以添加源的相同方式添加到目标中。然后CMake在链接阶段包含这些对象文件,就像编译目标的源文件创建的对象文件一样。这是使用 $<TARGET_OBJECTS:…>生成器表达式完成的,它以适合add_executable()或add_library()使用的形式列出了对象文件。

# Define an object library
add_library(objLib OBJECT src1.cpp src2.cpp)
# Define two executables which each have their own source
# file as well as the object files from objLib
add_executable(app1 app1.cpp $<TARGET_OBJECTS:objLib>)
add_executable(app2 app2.cpp $<TARGET_OBJECTS:objLib>)

​ 在上面的例子中,没有为objLib创建单独的库,但是src1.cpp和src2.cpp源文件仍然只编译一次。对于某些构建来说,这可能更方便,因为它可以避免创建静态库的构建时间成本或动态库的符号解析的运行时成本,但仍然可以避免多次编译相同的源代码。

9.3 一般的信息

​ 生成器表达式可以提供关于目标以外的信息。可以获得有关正在使用的编译器、正在构建目标的平台、构建配置的名称等信息。这类表达式倾向于在更高级的情况下使用,例如处理自定义编译器或解决特定编译器或工具链的特定问题。这些表达式也会引起误用,因为它们似乎提供了一种方法来构造路径,而这些路径本可以通过更健壮的方法(如使用TARGET_FILE表达式或其他CMake特性)获得。在依赖更通用的信息生成器表达式作为解决问题的方法之前,开发人员应该仔细考虑。也就是说,有些表达确实有正当的用途。这里列出了一些更常见的表达式和一些实用程序表达式,作为进一步阅读的起点:

  • $<CONFIG>

    计算结果为生成类型。优先使用CMAKE_BUILD_TYPE变量,因为该变量不会在Xcode或Visual Studio等多配置项目生成器中使用。CMake的早期版本使用了现在已弃用的$<CONFIGURATION>表达式,但项目现在应该只使用$<CONFIG>。

  • $<PLATFORM_ID>

    标识正在为其构建目标的平台。这在交叉编译的情况下非常有用,特别是当一个构建可能支持多个平台(例如设备和模拟器构建)时。这个生成器表达式与CMAKE_SYSTEM_NAME变量密切相关,项目应该考虑在特定的情况下使用该变量是否会更简单。

  • $<C_COMPILER_VERSION>, $<CXX_COMPILER_VERSION>

    在某些情况下,只在编译器版本比某个特定版本旧或新时添加内容可能会有用。这可以通过 $<VERSION_???:…>生成器表达式。例如,如果c++编译器的版本小于4.2.0,要生成字符串OLD_COMPILER,可以使用以下表达式:

    $<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,4.2.0>:OLD_COMPILER>
    

    这样的表达式往往只在以下情况下使用:已知编译器的类型,并且编译器的特定行为需要由项目以某种特殊的方式处理。在特定的情况下,它可能是一种有用的技术,但如果过于依赖这些表达式,它可能会降低项目的可移植性。

  • $<LOWER_CASE:…>, $<UPPER_CASE:…>

    任何内容都可以通过这些表达式转换为小写或大写。这在执行字符串比较之前是非常有用的。例如:

    $<STREQUAL:$<UPPER_CASE:${someVar}>,FOOBAR>
    

相关代码:https://gitee.com/jiangli01/cmake-learning
更多请关注微信公众号【Hope Hut】:
在这里插入图片描述

热门文章

暂无图片
编程学习 ·

exe4j详细使用教程(附下载安装链接)

一、exe4j介绍 ​ exe4j是一个帮助你集成Java应用程序到Windows操作环境的java可执行文件生成工具&#xff0c;无论这些应用是用于服务器&#xff0c;还是图形用户界面&#xff08;GUI&#xff09;或命令行的应用程序。如果你想在任务管理器中及Windows XP分组的用户友好任务栏…
暂无图片
编程学习 ·

AUTOSAR从入门到精通100讲(126)-浅谈车载充电系统通信方案

01 引言 本文深入研究车载充电系统策略,设计出一套基于电动汽车电池管理系统与车载充电机的CAN通信协议,可供电动汽车设计人员参考借鉴。 02 电动汽车充电系统通讯网络 电动汽车整车控制系统中采用的是CAN总线通信方式,由一个整车内部高速CAN网络、内部低速CAN网络和一个充电…
暂无图片
编程学习 ·

47.第十章 网络协议和管理配置 -- 网络配置(八)

4.3.3 route 命令 路由表管理命令 路由表主要构成: Destination: 目标网络ID,表示可以到达的目标网络ID,0.0.0.0/0 表示所有未知网络,又称为默认路由,优先级最低Genmask:目标网络对应的netmaskIface: 到达对应网络,应该从当前主机哪个网卡发送出来Gateway: 到达非直连的网络,…
暂无图片
编程学习 ·

元宇宙技术基础

请看图&#xff1a; 1、通过AR、VR等交互技术提升游戏的沉浸感 回顾游戏的发展历程&#xff0c;沉浸感的提升一直是技术突破的主要方向。从《愤怒的小鸟》到CSGO,游戏建模方式从2D到3D的提升使游戏中的物体呈现立体感。玩家在游戏中可以只有切换视角&#xff0c;进而提升沉浸…
暂无图片
编程学习 ·

flink的伪分布式搭建

一 flink的伪分布式搭建 1.1 执行架构图 1.Flink程序需要提交给 Job Client2.Job Client将作业提交给 Job Manager3.Job Manager负责协调资源分配和作业执行。 资源分配完成后&#xff0c;任务将提交给相应的 Task Manage。4.Task Manager启动一个线程以开始执行。Task Manage…
暂无图片
编程学习 ·

十进制正整数与二进制字符串的转换(C++)

Function one&#xff1a; //十进制数字转成二进制字符串 string Binary(int x) {string s "";while(x){if(x % 2 0) s 0 s;else s 1 s;x / 2;}return s; } Function two&#xff1a; //二进制字符串变为十进制数字 int Decimal(string s) {int num 0, …
暂无图片
编程学习 ·

[含lw+源码等]微信小程序校园辩论管理平台+后台管理系统[包运行成功]Java毕业设计计算机毕设

项目功能简介: 《微信小程序校园辩论管理平台后台管理系统》该项目含有源码、论文等资料、配套开发软件、软件安装教程、项目发布教程等 本系统包含微信小程序做的辩论管理前台和Java做的后台管理系统&#xff1a; 微信小程序——辩论管理前台涉及技术&#xff1a;WXML 和 WXS…
暂无图片
编程学习 ·

树莓派驱动DHT11温湿度传感器

1&#xff0c;直接使用python库 代码如下 import RPi.GPIO as GPIO import dht11 import time import datetimeGPIO.setwarnings(True) GPIO.setmode(GPIO.BCM)instance dht11.DHT11(pin14)try:while True:result instance.read()if result.is_valid():print(ok)print(&quo…
暂无图片
编程学习 ·

ELK简介

ELK简介 ELK是三个开源软件的缩写&#xff0c;Elasticsearch、Logstash、Kibana。它们都是开源软件。不过现在还新增了一个 Beats&#xff0c;它是一个轻量级的日志收集处理工具(Agent)&#xff0c;Beats 占用资源少&#xff0c;适合于在各个服务器上搜集日志后传输给 Logstas…
暂无图片
编程学习 ·

Linux 基础

通常大数据框架都部署在 Linux 服务器上&#xff0c;所以需要具备一定的 Linux 知识。Linux 书籍当中比较著名的是 《鸟哥私房菜》系列&#xff0c;这个系列很全面也很经典。但如果你希望能够快速地入门&#xff0c;这里推荐《Linux 就该这么学》&#xff0c;其网站上有免费的电…
暂无图片
编程学习 ·

Windows2022 无线网卡装不上驱动

想来 Windows2022 和 windows10/11 的驱动应该差不多通用的&#xff0c;但是死活装不上呢&#xff1f; 搜一下&#xff0c;有人提到 “默认安装时‘无线LAN服务’是关闭的&#xff0c;如果需要开启&#xff0c;只需要在“添加角色和功能”中&#xff0c;选择开启“无线LAN服务…
暂无图片
编程学习 ·

【嵌入式面试宝典】版本控制工具Git常用命令总结

目录 创建仓库 查看信息 版本回退 版本检出 远程库 Git 创建仓库 git initgit add <file> 可反复多次使用&#xff0c;添加多个文件git commit -m <message> 查看信息 git status 仓库当前的状态git diff 差异对比git log 历史记录&#xff0c;提交日志--pret…
暂无图片
编程学习 ·

用Postman生成测试报告

newman newman是一款基于nodejs开发的可以运行postman脚本的工具&#xff0c;使用Newman&#xff0c;可以直接从命令运行和测试postman集合。 安装nodejs 下载地址&#xff1a;https://nodejs.org/en/download/ 选择自己系统相对应的版本内容进行下载&#xff0c;然后傻瓜式安…
暂无图片
编程学习 ·

Java面向对象之多态、向上转型和向下转型

文章目录前言一、多态二、引用类型之间的转换Ⅰ.向上转型Ⅱ.向下转型总结前言 今天继续Java面向对象的学习&#xff0c;学习面向对象的第三大特征&#xff1a;多态&#xff0c;了解多态的意义&#xff0c;以及两种引用类型之间的转换&#xff1a;向上转型、向下转型。  希望能…
暂无图片
编程学习 ·

IntelliJ IDEA 的 Metamodel 配置

如果不在 IntelliJ IDEA 中配置的话&#xff0c;将会出现编译错误。 这是因为 Metamodel 源代码不是在 src 目录中的&#xff0c;而是在编译的时候生成的。 要解决这个问题其实也非常简单&#xff0c;按照下面 2 个步骤就可以了。 配置 首先需要进入进入 IntelliJ IDEA 的编…