【写在前面】
生成器表达式在构建系统生成期间进行评估,以生成特定于每个构建配置的信息。它们的形式为 $<...>
。例如:
target_include_directories(tgt PRIVATE /opt/include/$<CXX_COMPILER_ID>)
这将扩展为 “/opt/include/GNU”、“/opt/include/Clang”等,具体取决于所使用的 C++ 编译器。
许多目标属性的上下文中允许使用生成器表达式,例如: prop_tgt:LINK_LIBRARIES、 INCLUDE_DIRECTORIES、 COMPILE_DEFINITIONS 等。它们也可以在使用命令填充这些属性时使用,例如: command:target_link_libraries、 target_include_directories()、 target_compile_definitions() 等。它们启用条件链接、编译时使用的条件定义、条件包含目录等。这些条件可能基于构建配置、目标属性、平台信息或任何其他可查询信息。
生成器表达式可以嵌套:
target_compile_definitions(tgt PRIVATE $<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,4.2.0>:OLD_COMPILER>)
如果 CMAKE_CXX_COMPILER_VERSION 小于 4.2.0,则以上将扩展为 OLD_COMPILER
。
【正文开始】
官方对其的介绍:
生成器表达式通常在命令参数之后进行解析。如果生成器表达式包含空格、换行符、分号或其他可能被解释为命令参数分隔符的字符,则在传递给命令时,整个表达式应该用引号括起来。如果不这样做可能会导致表达式被拆分并且它可能不再被识别为生成器表达式。
使用 add_custom_command() 或 add_custom_target() 时,请使用
VERBATIM
和COMMAND_EXPAND_LISTS
选项以获得可靠的参数拆分和引用。# WRONG: Embedded space will be treated as an argument separator. # This ends up not being seen as a generator expression at all. add_custom_target(run_some_tool COMMAND some_tool -I$<JOIN:$<TARGET_PROPERTY:tgt,INCLUDE_DIRECTORIES>, -I> VERBATIM )
# Better, but still not robust. Quotes prevent the space from splitting the # expression. However, the tool will receive the expanded value as a single # argument. add_custom_target(run_some_tool COMMAND some_tool "-I$<JOIN:$<TARGET_PROPERTY:tgt,INCLUDE_DIRECTORIES>, -I>" VERBATIM )
# Nearly correct. Using a semicolon to separate arguments and adding the # COMMAND_EXPAND_LISTS option means that paths with spaces will be handled # correctly. Quoting the whole expression ensures it is seen as a generator # expression. But if the target property is empty, we will get a bare -I # with nothing after it. add_custom_target(run_some_tool COMMAND some_tool "-I$<JOIN:$<TARGET_PROPERTY:tgt,INCLUDE_DIRECTORIES>,;-I>" COMMAND_EXPAND_LISTS VERBATIM )
使用变量构建更复杂的生成器表达式也是减少错误和提高可读性的好方法。上面的例子可以像这样进一步改进:
# The $<BOOL:...> check prevents adding anything if the property is empty, # assuming the property value cannot be one of CMake's false constants. set(prop "$<TARGET_PROPERTY:tgt,INCLUDE_DIRECTORIES>") add_custom_target(run_some_tool COMMAND some_tool "$<$<BOOL:${prop}>:-I$<JOIN:${prop},;-I>>" COMMAND_EXPAND_LISTS VERBATIM )
一个常见的错误是尝试通过缩进将生成器表达式拆分为多行:
# WRONG: New lines and spaces all treated as argument separators, so the # generator expression is split and not recognized correctly. target_compile_definitions(tgt PRIVATE $<$<AND: $<CXX_COMPILER_ID:GNU>, $<VERSION_GREATER_EQUAL:$<CXX_COMPILER_VERSION>,5> >:HAVE_5_OR_LATER> )
同样,使用具有精心选择的名称的辅助变量来构建一个可读的表达式:
set(is_gnu "$<CXX_COMPILER_ID:GNU>") set(v5_or_later "$<VERSION_GREATER_EQUAL:$<CXX_COMPILER_VERSION>,5>") set(meet_requirements "$<AND:${is_gnu},${v5_or_later}>") target_compile_definitions(tgt PRIVATE "$<${meet_requirements}:HAVE_5_OR_LATER>" )
由于生成器表达式是在构建系统生成期间计算的,而不是在处理 CMakeLists.txt 文件期间计算的,因此无法使用 message() 命令检查它们的结果。生成调试消息的一种可能方法是添加自定义目标:
add_custom_target(genexdebug COMMAND ${CMAKE_COMMAND} -E echo "$<...>")
运行 cmake 后,您可以构建
genexdebug
目标以打印$<...>
表达式的结果(即运行命令:option:cmake --build ... --target genexdebug <cmake--build --target>)。另一种方法是使用 file(GENERATE) 将调试消息写入文件:
file(GENERATE OUTPUT filename CONTENT "$<...>")
从官方文档看,cmake 生成表达式的种类非常之多:
因此,我将这部分内容分为多篇文章进行讲解。
【条件表达式和逻辑运算符】
文章链接:
【结语】
项目链接(多多star呀..⭐_⭐):