目录
-
一 概述
-
二 常用的生成器表达式
-
1 布尔生成器表达式
-
2 字符串值生成器表达式
-
3 调试
-
CMake的生成器表达式不算是特别常用,但是有一些场景可能是必须要使用的;或者在针对不同编译类型设置不同编译参数的时候可以巧妙应用,从而减少配置代码。
生成器表达式听起来稍微有点复杂,但是其实只需要掌握一些常用的功能就能够有所裨益,至于更加复杂的写法,在需要的时候研究一下即可。本文主要介绍下生成器表达式的概念、种类、和常用的一些生成器表达式。
一 概述
生成器表达式简单来说就是在CMake生成构建系统的时候根据不同配置动态生成特定的内容。比如:
-
条件链接,如针对同一个编译目标,debug版本和release版本链接不同的库文件
-
条件定义,如针对不同编译器,定义不同的宏
所以可以看到,其中的要点是条件,之所以需要自动生成,那绝大多数时候肯定是因为开发者无法提前确定某些配置,不能提前确定那往往就是有条件的。
生成器表达式的格式形如$<...>
,可以嵌套,可以用在很多构建目标的属性设置和特定的CMake命令中。值得强调的是,生成表达式被展开是在生成构建系统的时候,所以不能通过解析配置CMakeLists.txt阶段的message
命令打印,文末会介绍其调试方法。
二 常用的生成器表达式
1 布尔生成器表达式
逻辑运算符
逻辑运算很多语言都是需要的,CMake生成器表达式中有这些:
-
$<BOOL:string>
:如果字符串为空、0
;不区分大小写的FALSE
、OFF
、N
、NO
、IGNORE
、NOTFOUND
;或者区分大小写以-NOTFOUND
结尾的字符串,则为0,否则为1 -
$<AND:conditions>
:逻辑与,conditons
是以逗号分割的条件列表 -
$<OR:conditions>
:逻辑或,conditons
是以逗号分割的条件列表 -
$<NOT:condition>
:逻辑非
一般来说,条件是列表的,都是使用逗号进行分割,后面不再赘述。
字符串比较
-
$<STREQUAL:string1,string2>
:判断字符串是否相等 -
$<EQUAL:value1,value2>
:判断数值是否相等 -
$<IN_LIST:string,list>
:判断string
是否包含在list
中,list
使用分号分割
注意这里的
list
是在逗号后面的列表,所以其内容需要使用分号分割。
变量查询
这个会是比较常用的,在实际使用的时候会根据不同CMake内置变量生成不同配置,核心就在于“判断”:
-
$<TARGET_EXISTS:target>
:判断目标是否存在 -
$<CONFIG:cfgs>
:判断编译类型配置是否包含在cfgs
列表(比如"release,debug")中;不区分大小写 -
$<PLATFORM_ID:platform_ids>
:判断CMake定义的平台ID是否包含在platform_ids
列表中 -
$<COMPILE_LANGUAGE:languages>
:判断编译语言是否包含在languages
列表中
2 字符串值生成器表达式
请注意,前面都是铺垫,这里才是使用生成器表达式的主要目的:生成特定的字符串。比如官方的例子:基于编译器ID指定include目录:
include_directories(/usr/include/$<CXX_COMPILER_ID>/)
根据编译器的类型,$<CXX_COMPILER_ID>
会被替换成对应的ID(比如“GNU”、“Clang”)。
条件表达式
这便是本文的核心了,主要有两个格式:
-
$<condition:true_string>
:如果条件为真,则结果为true_string
,否则为空 -
$<IF:condition,str1,str2>
:如果条件为真,则结果为str1
,否则为str2
这里的条件一般情况下就是前面介绍的布尔生成器表达式。比如要根据编译类型指定不同的编译选项,可以像下面这样:
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -O0")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
但是使用生成器表达式可以简化成:
add_compile_options("$<$<CONFIG:Debug>:-g;-O0>")
add_compile_options($<$<CONFIG:Debug>:-O2>)
如果需要指定多个编译选项,必须使用双引号把生成器表达式包含起来,且选项之间使用分号。
后面这个方法适用于设置一些对所有编译器(取决于项目编译语言)都通用的编译选项,而需要设置一些编译器特有的选项时,通过设置指定编译器的编译选项(前一种方法)更加简洁明了。
当然,可以用表达式判断编译器ID设置不同编译选项,不过明显有些为了用而用,这是没必要的。
转义字符
这比较好理解,因为有一些字符有特殊含义,所以可能需要转义,比如常用的$<COMMA>
和$<SEMICOLON>
,分别表示,
和;
。
字符串操作
常用的有$<LOWER_CASE:string>
、$<UPPER_CASE:string>
用于转换大小写。
获取变量值
获取变量的值和前文提到的变量查询很类似,前面说的变量查询是判断是否存在于指定列表中或者等于指定值。语法格式是类似的,以CONFIG
为例:
-
获取变量值:
$<CONFIG>
-
判断是否存在于列表中:
$<CONFIG:cfgs>
详见Variable Queries:https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#id2。
编译目标查询
这里的查询是指获取编译目标(通过add_executable()
、add_library()
命令生成的)相关的一些信息,包括:
-
$<TARGET_FILE:tgt>
:获取编译目标的文件路径 -
$<TARGET_FILE_NAME:tgt>
:获取编译目标的文件名 -
$<TARGET_FILE_BASE_NAME:tgt>
:获取编译目标的基础名字,也就是文件名去掉前缀和扩展名
官网有更多详细介绍,有其他需要可以阅读:target-dependent-queries:https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#target-dependent-queries。
在本专题前一篇文章中介绍合并静态库的时候,就用到
$<TARGET_FILE:tgt>
去获取静态库的路径。
3 调试
调试可以通过输出到文件的方式,在cmake
执行完之后去检查是否符合预期,比如:
file(GENERATE OUTPUT "./generator_test.txt" CONTENT "$<$<CONFIG:Debug>:-g;-O0>,$<PLATFORM_ID>\n")
在MacOS中执行cmake
,得到的结果如下:
# cmake -B cmake-build -DCMAKE_BUILD_TYPE=Debug
...
# cat cmake-build/generator_test.txt
-g;-O0,Darwin
如果不想写文件,也可以添加一个自定义目标,比如:
add_custom_target(gentest COMMAND ${CMAKE_COMMAND} -E echo "\"$<$<CONFIG:Debug>:-g;-O0>,$<PLATFORM_ID>\"")
注意这里需要双引号转义,确保生成器表达式展开之后是字符串。
在执行cmake
之后,可以使用make gentest
输出到生成器表达式的内容:
# cd cmake-build && make gentest
-g;-O0,Darwin
Built target gentest
欧克,结了!