CMake应用:生成器表达式

目录

  • 一 概述

  • 二 常用的生成器表达式

    • 1 布尔生成器表达式

    • 2 字符串值生成器表达式

    • 3 调试

CMake的生成器表达式不算是特别常用,但是有一些场景可能是必须要使用的;或者在针对不同编译类型设置不同编译参数的时候可以巧妙应用,从而减少配置代码。

生成器表达式听起来稍微有点复杂,但是其实只需要掌握一些常用的功能就能够有所裨益,至于更加复杂的写法,在需要的时候研究一下即可。本文主要介绍下生成器表达式的概念、种类、和常用的一些生成器表达式。

一 概述

生成器表达式简单来说就是在CMake生成构建系统的时候根据不同配置动态生成特定的内容。比如:

  1. 条件链接,如针对同一个编译目标,debug版本和release版本链接不同的库文件

  2. 条件定义,如针对不同编译器,定义不同的宏

所以可以看到,其中的要点是条件,之所以需要自动生成,那绝大多数时候肯定是因为开发者无法提前确定某些配置,不能提前确定那往往就是有条件的。

生成器表达式的格式形如$<...>,可以嵌套,可以用在很多构建目标的属性设置和特定的CMake命令中。值得强调的是,生成表达式被展开是在生成构建系统的时候,所以不能通过解析配置CMakeLists.txt阶段的message命令打印,文末会介绍其调试方法。

二 常用的生成器表达式

1 布尔生成器表达式

逻辑运算符

逻辑运算很多语言都是需要的,CMake生成器表达式中有这些:

  1. $<BOOL:string>:如果字符串为空、0;不区分大小写的FALSEOFFNNOIGNORENOTFOUND;或者区分大小写以-NOTFOUND结尾的字符串,则为0,否则为1

  2. $<AND:conditions>:逻辑与,conditons是以逗号分割的条件列表

  3. $<OR:conditions>:逻辑或,conditons是以逗号分割的条件列表

  4. $<NOT:condition>:逻辑非

一般来说,条件是列表的,都是使用逗号进行分割,后面不再赘述。

字符串比较

  1. $<STREQUAL:string1,string2>:判断字符串是否相等

  2. $<EQUAL:value1,value2>:判断数值是否相等

  3. $<IN_LIST:string,list>:判断string是否包含在list中,list使用分号分割

注意这里的list是在逗号后面的列表,所以其内容需要使用分号分割。

变量查询

这个会是比较常用的,在实际使用的时候会根据不同CMake内置变量生成不同配置,核心就在于“判断”:

  1. $<TARGET_EXISTS:target>:判断目标是否存在

  2. $<CONFIG:cfgs>:判断编译类型配置是否包含在cfgs列表(比如"release,debug")中;不区分大小写

  3. $<PLATFORM_ID:platform_ids>:判断CMake定义的平台ID是否包含在platform_ids列表中

  4. $<COMPILE_LANGUAGE:languages>:判断编译语言是否包含在languages列表中

2 字符串值生成器表达式

请注意,前面都是铺垫,这里才是使用生成器表达式的主要目的:生成特定的字符串。比如官方的例子:基于编译器ID指定include目录:

include_directories(/usr/include/$<CXX_COMPILER_ID>/)

根据编译器的类型,$<CXX_COMPILER_ID>会被替换成对应的ID(比如“GNU”、“Clang”)。

条件表达式

这便是本文的核心了,主要有两个格式:

  1. $<condition:true_string>:如果条件为真,则结果为true_string,否则为空

  2. $<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为例:

  1. 获取变量值:$<CONFIG>

  2. 判断是否存在于列表中:$<CONFIG:cfgs>

详见Variable Queries:https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#id2。

编译目标查询

这里的查询是指获取编译目标(通过add_executable()add_library()命令生成的)相关的一些信息,包括:

  1. $<TARGET_FILE:tgt>:获取编译目标的文件路径

  2. $<TARGET_FILE_NAME:tgt>:获取编译目标的文件名

  3. $<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

欧克,结了!

上一篇:cmake函数 file


下一篇:如何编写一个合格的CMakeLists