CMake(九):生成器表达式

2023-05-16

当运行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】:
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CMake(九):生成器表达式 的相关文章

  • extern C的作用详解

    extern 34 C 34 的主要作用就是为了能够正确实现C 43 43 代码调用其他C语言代码 加上extern 34 C 34 后 xff0c 会指示编译器这部分代码按C语言的进行编译 xff0c 而不是C 43 43 的 由于C 4
  • Linux socket CAN编程示例

    如下所示 xff0c 代码展示了Linux下CAN的发送和接收 xff1a include lt stdio h gt include lt stdlib h gt include lt string h gt include lt uni
  • windows下面安装git

    注意在安装过程中选择override这个选项
  • QT中Map的使用

    Qt中的QMap介绍与使用 xff0c 在坛子里逛了一圈 xff0c 发现在使用QMap中 xff0c 出现过很多的问题 xff0c Map是一个很有用的数据结构 它以 键 值 的形式保存数据 在使用的时候 xff0c 通过提供字符标示 x
  • ubuntu 更新内核切换内核启动

    1 查看需要更新的内核命令 xff1a apt cache search linux 该命令将会显示所有可以获取的内核 2 安装内核 xff0c 假设你要安装的内核为2 6 39 0 xff0c 则使用下面的命令 sudo apt get
  • 多线程实现对同一个或多个文件的读写操作

    程序用途 xff1a 实现多个线程对同一文件的读写操作 程序代码 xff1a test c 该程序在Ubuntu下测试通过 include lt stdio h gt include lt pthread h gt include lt s
  • linux下设置共享目录

    Linux系统的文件或目录的共享功能是非常强大 xff0c 而且是非常灵活的 xff0c 其对权限的控制可以做到非常的细致 xff0c 当然如果你是通过命令行方式进行设置的 话 xff0c 那么对于刚接触linux系统的用户来说将是一件十分
  • shell 数组赋值

    shell编程 xff0c 给数组赋值及两个数组初始化与比较 bin sh output files 61 cat outfiles for i 61 0 i lt output files 64 43 43 i do echo 34 ar
  • vnc的两种配置方法及解决vnc连不上的情况

    1 vnc连不上的现象 xff1a Timed out waiting for a response from the computer 解决方法 xff1a sudo sbin iptables I INPUT 1 p TCP dport
  • linux制作本地镜像

    1 前提条件 xff1a 有安装linux系统的iso 2 添加yum文件 xff1a touch etc yum repos d iso repo iso name 61 CentOS releasever Media baseurl 6
  • 使用parted创建分区

    今天在网上查找分区方法 xff0c 发现都是用的fdisk xff0c 但自己使用总是出错 xff0c 后来请求大神帮忙 xff0c 发现了一个好用的工具 xff0c 这里把具体的使用过程记录下来 root 64 pc160 parted
  • “结构体名”和“结构体名是个指针”的区别

    经常看见下面这样的定义 xff1a typedef struct int a double b emp i pemp i typedef 了两个新的数据类型 xff08 结构体 xff09 xff0c 其中一个是指针方式的名字 int ma
  • 简答实用的宏的写法

    本篇文章主要实现打印参数的传递 xff0c 这里定义了一个宏 define debug printf format printf 34 s d 34 format 34 34 func LINE VA ARGS
  • RK1126从入门到放弃:(二)Buildroot说明

    一 目录介绍 buildroot arch 存放CPU架构相关的配置脚本 xff0c 如arm mips x86 xff0c 这些CPU相关的配置 xff0c 在制作工具链时 xff0c 编译uboot和kernel时很关键 board x
  • 二进制基础及位运算

    一 什么是二进制 二进制是计算机运算时所采用的数制 xff0c 基数是2 xff0c 也就是说它只有两个数字符号 xff0c 即0和1 如果在给定的数中 xff0c 除0和1外还有其他数 xff08 例如1061 xff09 xff0c 那
  • Django学习笔记2 HTTP协议

    HTTP协议 web前端系统和后端系统之间是通过HTTP协议进行通信的HTTP 协议全称是超文本传输协议 xff0c 英文是 Hypertext Transfer ProtocolHTTP 协议最大的特点是通讯双方分为客户端和服务端 由于目
  • VINS-Mono视觉初始化代码详解

    摘要 视觉初始化的过程是至关重要的 xff0c 如果在刚开始不能给出很好的位姿态估计 xff0c 那么也就不能对IMU的参数进行精确的标定 这里就体现了多传感器融合的思想 xff0c 当一个传感器的数据具有不确定性的时候 xff0c 我们需
  • 5G DTU 数据上传 无线通信

    5G数据采集上传DTU xff0c 和各种使用串口通信的用户设备进行连接 xff0c 通过无线网络进行数据云端上传实现远程实时在线监测 采用心跳包保持永久在线 xff0c 支持断线自动重连 自动重拨号等特点 5G DTU组网迅速灵活 xff
  • 5G DTU地下水水位监测

    地下水监管不当会造成地面沉降 地裂缝 岩溶塌陷 海水入侵 水质污染等危害 xff0c 掌握了解地下水水位 分布等状态 xff0c 响应可持续发展的号召 xff0c 提高地下水资源科学保护和合理利用 计讯物联地下水水位监测系统平台采用5GDT
  • 5g DTU 无线数传终端应用

    DTU无线数传终端TD210全网通2G 3G 4G网络 xff0c 实现串口数据与IP数据的转换 xff0c DTU作为串口数据的无线终端设备 xff0c 可广泛应用于各行各业 DTU无线数传终端TD210应用 第一 xff0c 农业领域

随机推荐

  • 使用L298N电机驱动器和Arduino控制步进电机

    在本文中 xff0c 您将学习如何使用L298N电动机驱动器控制步进电动机 该驱动板通常用于控制直流电动机 xff0c 但它还是控制步进电动机的廉价替代品 xff01 它可以控制大多数步进电机 xff08 例如NEMA 17 xff09 的
  • STM32串口通讯(接收完成一整个数据帧再将数据发送出去)

    STM32串口通信可以分为查询 xff0c 中断 xff0c DMA三种方式进行通讯 xff0c 本文主要就中断的方式进行讲解 采用中断的方式进行通讯时 xff0c 可以使能接受非空中断 xff08 RXNE xff09 xff0c 当接收
  • 树的先序、中序、后序遍历

    遍历分分先序 中序 后序 先序 xff1a 先访问根结点 左结点 右结点 中序 xff1a 先访问左结点 根结点 右结点 后序 xff1a 先访问左结点 右结点 根结点 先序 xff1a ABC 中序 xff1a BAC 后序 xff1a
  • 调整Arduino STM32的串口缓存大小的方法

    通常Arduino中调整串口缓存大小的方法是修改HardwareSerial h中的常量 其实根本无需修改系统core中的定义值 xff0c 只需要在代码最上方添加以下常量定义 xff0c 抢在HardwareSerial h之前定义缓存大
  • C++入门学习(头文件)

    1 C 43 43 中的头文件 1 1 标准库中的头文件 C 43 43 标准库中的一切内容都被放在名字空间std中 xff08 名字空间中的内容对外是不可见的 xff09 xff0c 但是带来了一个新问题 xff0c 无数现有的C 43
  • 用for循环实现delay延时原理

    void Delay10ms unsigned int c 误差 0us unsigned char a b for c gt 0 c c可以不用初始化 xff0c 因为默认传的参数即为初始化 for b 61 38 b gt 0 b fo
  • 解决ROS中运行gazebo出现process has died的情况

    项目场景 xff1a gazebo 1 process has died pid 397 exit code 255 cmd opt ros melodic lib gazebo ros gzserver e ode worlds empt
  • 使用Ventoy制作U盘启动项

    最近在安装linux镜像的时候遇到了使用UltraISO软件制作U盘启动盘无法使用的情况 下面介绍另外一个软件把U盘制作成启动盘Ventoy xff1a 下载地址 xff1a Ventoy 使用方法 xff1a 1 下载好Ventoy xf
  • git 快速入手

    目录 一 xff1a 初次使用git及github 二 xff1a 将github上下载的代码上传到自己的github仓库里 三 xff1a 使用HTTP上传自己写的项目至github git常用指令汇总 使用需求 xff1a 初次接触gi
  • 线程、进程、并发、cpu、gpu的联系

    1 线程和进程的区别 进程 xff1a 一个在内存中运行的应用程序 每个进程都有自己独立的一块内存空间 xff0c 一个进程可以有多个线程 比如在Windows系统中 xff0c 一个运行的xx exe就是一个进程 线程 xff1a 进程中
  • ubuntu系统安装cuda、cudnn、pytorch和libtorch

    1 安装cuda和cudnn 本机安装的cuda版本11 0 2 cudnn版本 v8 0 5 cu11 0 Ubuntu20 04下CUDA cuDNN的详细安装与配置过程 xff08 图文 xff09 ubuntu20 04安装cuda
  • 深度学习语法篇

    一 基本常识 图像的分辨率的通道数 分辨率和通道数是两个不同的概念 分辨率指的是图像的像素数量 xff0c 它反映了图像的清晰度和细节程度 例如 xff0c 一个分辨率为64x64的图像意味着它有64个像素行和64个像素列 xff0c 总共
  • 第二讲:线性表示及坐标

    第二讲 xff1a 线性表示及坐标 一 线性表示 1 线性表示定义 xff1a 设 是线性空间V中的向量 xff0c 若存在V中一组向量 1 xff0c 2 xff0c xff0c n xff0c 及一组数x1 xff0c x2 xff0c
  • 快速理解掌握指针

    p gt next 61 q 像这种语句 xff0c 表示改变了p后面的连接关系 p 61 q gt next 这类语句 xff0c 没改变连接关系 xff0c 只是赋值而已 解读代码中指针所代表的节点之间的前后连接关系 只要输出该指针对应
  • 第三讲:子空间

    第三讲 xff1a 子空间 一 子空间定义 1 子空间 xff1a 设V是数域F上的线性空间 xff0c W是V的子集 xff0c 若对W中的任意元素 xff0c 及数K F xff0c 按V中的加法和数乘有 xff1a 1 xff09 4
  • Qt多线程之线程之间的传递数据

    hpp span class token macro property span class token directive keyword ifndef span MAINWINDOW H span span class token ma
  • 循环队列c代码实现

    循环队列的抽象数据类型 ADT 队列 xff08 Queue xff09 Data 同线性表 元素具有相同的类型 xff0c 相邻元素具有前驱和后继的关系 Operator span class token function InitQue
  • CMake(四):变量

    前面展示了如何定义基本目标和生成构建输出 就其本身而言 xff0c 这已经很有用了 xff0c 但CMake还附带了一大堆其他特性 xff0c 这些特性带来了极大的灵活性和便利性 本章涵盖了CMake最基本的部分之一 xff0c 即变量的使
  • CMake(六):使用子目录

    对于简单的项目 xff0c 将所有内容保存在一个目录中是可以的 xff0c 但是大多数实际项目倾向于将它们的文件分割到多个目录中 通常可以找到不同的文件类型或分组在各自的目录下的独立模块 xff0c 或者将属于逻辑功能组的文件放在项目目录层
  • CMake(九):生成器表达式

    当运行CMake时 xff0c 开发人员倾向于认为它是一个简单的步骤 xff0c 需要读取项目的CMakeLists txt文件 xff0c 并生成相关的特定于生成器的项目文件集 例如Visual Studio解决方案和项目文件 xff0c