目录

CMake生成器表达式

生成器表达式不在配置阶段计算,而是推迟到生成阶段,当项目文件被写入时再进行计算。它可以用来执行条件逻辑,输出字符串,提供有关构建的各个方面的信息。

简单布尔逻辑

生成器表达式是用$<expr>的语法指定的,角括号之间的内容可以有几种不同的形式。最简单的条件生成器表达式有以下几种:

1
2
3
$<1:expr>
$<0:expr>
$<BOOL:expr>
  • $<1:expr>:表达式的结果为expr
  • $<0:expr>:expr被忽略,表达式的结果是一个空字符串。
  • $<BOOL:expr>:当expr为false时返回0,否则返回1。

生成器表达式也支持逻辑运算:

1
2
3
$<AND:expr[,expr...]>
$<OR:expr[,expr...]>
$<NOT:expr>

expr的值只能为0或1。因为AND、OR和NOT要求expr的值只能为0或1,可以考虑用$<BOOL:...>来包装这些表达式。

在CMake 3.8及以后的版本中,if-then-else的逻辑可以用$<IF:...>表达式非常方便地表达出来:

1
$<IF:expr,val1,val0>

而在CMake 3.8之前,这个逻辑只能用以下更冗长的方式来表达:

1
$<expr:val1>$<$<NOT:expr>:val0>

生成器表达式可以被嵌套,允许构建任意复杂的表达式。下表是一些例子:

表达式 结果 说明
$<1:foo> foo
$<0:foo>
$true:foo Error, not a 1 or 0
$<$BOOL:true:foo> foo
$<$NOT:0:foo> foo
$<$NOT:1:foo>
$<$NOT:true:foo> foo Error, NOT requires a 1 or 0
$<$AND:1,0:foo>
$<$OR:1,0:foo> foo
$<1:$<$BOOL:false:foo»
$<IF:$BOOL:${foo},yes,no> yes or no 结果取决于${foo}。

和if命令一样,生成器表达式也支持对字符串、数字和版本测试。如下:

1
2
3
4
5
$<STREQUAL:string1,string2>
$<EQUAL:number1,number2>
$<VERSION_EQUAL:version1,version2>
$<VERSION_GREATER:version1,version2>
$<VERSION_LESS:version1,version2>

另一个非常有用的条件表达式是测试构建类型:

1
$<CONFIG:arg>

如果arg与实际的构建类型相对应,表达式计算为1,否则为0。常见的用法是为构建提供编译开关,比如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
add_executable(myApp src1.cpp src2.cpp)

# Before CMake 3.8
target_link_libraries(myApp PRIVATE
    $<$<CONFIG:Debug>:checkedAlgo>
    $<$<NOT:$<CONFIG:Debug>>:fastAlgo>
)

# CMake 3.8 or later allows a more concise form
target_link_libraries(myApp PRIVATE
    $<IF:$<CONFIG:Debug>,checkedAlgo,fastAlgo>
)

目标详情

生成器表达式的另一个常见用途是提供关于目标的信息。目标的任何属性都可以通过以下两种形式中的一种获得:

1
2
$<TARGET_PROPERTY:target,property>
$<TARGET_PROPERTY:property>

第一种形式从指定的目标中获取属性值,第二种形式从正在使用生成器表达式的目标中获取属性值。虽然TARGET_PROPERTY是一个非常灵活的表达式类型,但它并不总是获取目标信息的最佳方式。CMake还提供了其他表达式,比如以下几个表达式:

  • TARGET_FILE:获取目标二进制文件的绝对路径和文件名。
  • TARGET_FILE_NAME:TARGET_FILE相同,但不包括路径。
  • TARGET_FILE_DIR:与TARGET_FILE相同,但不包括文件名,这是获得目标目录的最可靠的方法。

除了TARGET_FILE表达式之外,CMake还提供了一些特定于库的表达式,这些表达式的名字以TARGET_LINKER_FILE和TARGET_SONAME_FILE开头。

CMake允许将一个库目标定义为一个对象库,它不是通常意义上的库,只是一个对象文件的集合。因为它们是对象文件,所以不能作为一个单元被链接。相反,它们必须以添加源文件的相同方式添加到目标中。然后CMake在链接阶段包括这些对象文件,就像编译目标的源文件所创建的对象文件一样。这是用$<TARGET_OBJECTS:...>生成器表达式完成的,它以适合add_executable或add_library使用的形式列出对象文件。如下:

1
2
3
4
5
6
7
# Define an object library
add_library(objLib OBJECT src1.cpp src2.cpp)

# Define two executables which each have their own source
# file as well as the object files from objLib
add_executable(app1 app1.cpp $<TARGET_OBJECTS:objLib>)
add_executable(app2 app2.cpp $<TARGET_OBJECTS:objLib>)

通用表达式

以下列出一些比较常见的表达式:

  • $<CONFIG>:测试构建类型。优先使用这个,而不是CMAKE_BUILD_TYPE变量,因为该变量不能用于多配置项目生成器。
  • $<PLATFORM_ID>:识别构建目标的平台。这在交叉编译的情况下很有用,特别是当一个构建可能支持多个平台时。这个表达式与CMAKE_SYSTEM_NAME变量密切相关。
  • $<C_COMPILER_VERSION>$<CXX_COMPILER_VERSION>:在某些情况下,我们可能想用特定版本的编译器。比如$<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,4.2.0>:OLD_COMPILER>
  • $<LOWER_CASE:…>$<UPPER_CASE:…>:转换大小写,常用于字符串比较,比如$<STREQUAL:$<UPPER_CASE:${someVar}>,FOOBAR>