23 生成器表达式
生成器表达式
[TOC]
1. 前言
1.1 CMake 工作流程
CMake 工作流程分为两步,配置阶段和生成阶段。我们在命令行中输入cmake ..
的时候,可以在输出内容的最后看到这样的两行话
-- Configuring done |
分别指代了这两个阶段
Configure 阶段
在这个阶段,CMake 会读取你的CMakeLists.txt
文件,并执行其中的命令。这些命令可能会检查系统的特性(例如,查找库或者编译器特性),设置变量,或者定义构建目标(例如库、可执行文件,或者其他伪目标)。这个阶段的主要目的是确定构建过程需要的所有信息。
Generate 阶段
在此阶段 CMake 会生成实际的建构档。这些文件包含了实际编译和链接你的代码所需要的命令,包括
-
各种编译选项
-
与目标相关的操作
比方说
target_include_directories( |
这两个命令均生效于生成阶段。而后文准备介绍的生成器表达式,就作用于生成阶段。也正因如此,只有能够在生成阶段生效的命令才能使用生成器表达式,而在配置阶段生效的命令,例如用于设置变量的 set
命令并不支持生成器表达式。
1.2 CMake 的生成器表达式是什么
生成器表达式在生成构建系统期间进行计算,以生成特定于每个生成配置的信息。他们有如下形式:$<...>
。
可以将其看作是一个if语句,或者说是一个三元表达式。
1.3 为什么要引入生成器表达式
在设置某些编译选项的时候,如果当条件较多,要制作的编译选项版本也相应增多。这时可以使用生成器表达式语法,让一部分编译信息在生成阶段自动完成,无需在配置阶段设置。
下面是一个示例,演示了在生成阶段代替配置阶段的表达式:
# Configure 阶段:给Debug和Release模式指定编译条件 |
这里的add_compile_options
就属于在生成阶段生效的命令。以上代码属于条件定义,即在不同的条件下生成不同的变量、宏等内容。其中这段代码使用生成器表达式的效果是,在不同的构建类型下(Debug、Release),会生成不同的被添加进编译选项的字符串。
2. 常用生成器表达式
2.1 布尔生成器表达式
2.1.1 逻辑运算符
-
$<BOOL:string>
如果字符串为空、
0
、不区分大小写的FALSE
、OFF
、N
、NO
、IGNORE
、NOTFOUND
,或者区分大小写的以-NOTFOUND
结尾的字符串,则表达式的值为0
,否则为1
。add_library(myLib ${SOURCES})
target_compile_definitions(myLib
PUBLIC $<$<BOOL:${USE_OTHERLIB}>:USE_OTHERLIB>
)在这个例子中,
$<BOOL:${USE_OTHERLIB}>
会检查变量USE_OTHERLIB
是否为假。如果USE_OTHERLIB
为假,那么这个表达式就会返回0
,否则返回1
。然后,$<1:USE_OTHERLIB>
会在前面的表达式为真时添加一个编译定义USE_OTHERLIB
。 -
$<AND:conditions>
:逻辑与conditons
是以逗号分割的条件列表,一般来说,条件是列表的,都是使用逗号进行分割,后文不再赘述。逻辑运算条件成立,表达式的值为0
,否则为1
,后文不再赘述。add_library(myLib ${SOURCES})
target_compile_definitions(myLib
PUBLIC $<$<AND:$<CONFIG:Debug>,$<TARGET_EXISTS:otherlib>>:USE_OTHERLIB>
)在这个例子中,
$<AND:$<CONFIG:Debug>,$<TARGET_EXISTS:otherlib>>
会在当前配置为Debug
并且目标otherlib
存在时返回1
,否则返回0
。然后,$<1:USE_OTHERLIB>
会在前面的表达式为真时添加一个编译定义USE_OTHERLIB
。 -
$<OR:conditions>
:逻辑或在给定的任何条件为真时返回
1
,否则返回0
。在这个例子中,
$<OR:$<CONFIG:Debug>,$<TARGET_EXISTS:otherlib>>
会在当前配置为 Debug 或者目标otherlib
存在时返回 1,否则返回 0。然后,$<1:USE_OTHERLIB>
会在前面的表达式为真时添加一个编译定义USE_OTHERLIB
。 -
$<NOT:condition>
:逻辑非在给定的条件为假时返回
1
,否则返回0
在这个例子中,
$<NOT:$<TARGET_EXISTS:otherlib>>
会在目标otherlib
不存在时返回1
,否则返回0
。然后,$<1:USE_OTHERLIB>
会在前面的表达式为真时添加一个编译定义USE_OTHERLIB
。
2.1.2 字符串比较
-
$<STREQUAL:string1,string2>
判断字符串是否相等
-
$<EQUAL:value1,value2>
判断数值是否相等
-
$<IN_LIST:string,list>
判断
string
是否包含在list
中,list
使用分号分割
#注意事项
这里的
list
是在逗号后面的列表,即${...}
所表示的内容,因此其内容需要使用分号分割。
2.1.3 变量查询
-
$<TARGET_EXISTS:target>
判断目标是否存在
-
$<CONFIG:cfgs>
判断编译类型配置是否包含在
cfgs
列表(比如"Release,Debug")中;不区分大小写 -
$<PLATFORM_ID:platform_ids>
判断CMake定义的平台ID是否包含在
platform_ids
列表中 -
$<COMPILE_LANGUAGE:languages>
判断编译语言是否包含在
languages
列表中
2.2 字符串生成器表达式
使用生成器表达式的主要目的可能就是因为此功能了。比如 CMake 官方的例子:基于编译器 ID 指定include目录:
include_directories("/usr/include/$<CXX_COMPILER_ID>/") |
根据编译器的类型,$<CXX_COMPILER_ID>
会被替换成对应的ID(比如“GNU”、“Clang”)。
2.2.1 条件表达式
-
$<condition:true_string>
如果条件为真,则结果为
true_string
,否则为空 -
$<IF:condition,str1,str2>
如果条件为真,则结果为
str1
,否则为str2
2.2.2 字符串转换
-
$<LOWER_CASE:string>
将字符串转为小写
add_library(mylib ${SOURCES})
set(TARGET_NAME "MYLIB")
target_compile_definitions(mylib PUBLIC
TARGET_NAME=$<LOWER_CASE:${TARGET_NAME}>
)在这个例子中,
$<LOWER_CASE:${TARGET_NAME}>
会将TARGET_NAME
变量的值转换为小写。然后,这个小写的值会被赋给编译定义TARGET_NAME
。所以,
mylib
会被编译时定义TARGET_NAME
为mylib
。 -
$<UPPER_CASE:string>
,将字符串转为大写
生成器表达式种类繁多,这里不再一一列举