cmake基础教程(40)生成器表达式

Posted 奇妙之二进制

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了cmake基础教程(40)生成器表达式相关的知识,希望对你有一定的参考价值。

参考:https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html

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>

编译目标查询

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

  1. $<TARGET_FILE:tgt>:获取编译目标的文件路径
  2. $<TARGET_FILE_NAME:tgt>:获取编译目标的文件名
  3. $<TARGET_FILE_BASE_NAME:tgt>:获取编译目标的基础名字,也就是文件名去掉前缀和扩展名

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

TARGET_FILE

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

TARGET_FILE_NAME

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

TARGET_FILE_DIR

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

导出和安装

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基础教程(40)生成器表达式的主要内容,如果未能解决你的问题,请参考以下文章

CMake基础教程cmake生成debug和release两个版本程序(如何编译-g版本)

CMake基础教程(28)自动生成目标依赖图

CMake基础教程(30)CMake构建系统概览

CMake基础教程(30)CMake构建系统概览

CMake基础教程(25)add_library生成库

cmake基础教程(42)configure_file动态生成头文件