目录

CMake流程控制

if命令

if命令语法如下:

1
2
3
4
5
6
7
if(expression1)
	# commands ...
elseif(expression2)
	# commands ...
else()
	# commands ...
endif()

if和elseif命令中的表达式可以有多种不同的形式。

基本表达式

最基本的表达式是一个常数:

1
if(value)

CMake判断真假的逻辑比大多数编程语言要复杂一些。对于一个没有引号的常量,规则如下:

  • 如果value值为1、ON、YES、TRUE、Y或一个非零的数字,那么它将被视为true,测试不区分大小写。
  • 如果value值为0、OFF、NO、FALSE、N、IGNORE、NOTFOUND、空字符串或以-NOTFOUND结尾的字符串,它将被视为false,同样不区分大小写。
  • 如果以上两种情况都不适用,它将被视为一个变量名或者字符串,并进一步评估。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# Examples of unquoted constants
if(YES)
if(0)
if(TRUE)

# These are also treated as unquoted constants because the
# variable evaluation occurs before if() sees the values
set(A YES)
set(B 0)
if(${A}) # Evaluates to true
if(${B}) # Evaluates to false

# Does not match any of the true or false constants, so proceed
# to testing as a variable name in the fall through case below
if(someLetters)

# Quoted value, so bypass the true/false constant matching
# and fall through to testing as a variable name or string
if("someLetters")

CMake文档中提到的"fall through"案例为以下形式:

1
if(<variable|string>)

表达式为以下两种情况之一:

  • 一个没有引号的(可能是未定义的)变量的名称。
  • 一个带引号的字符串。

当使用未加引号的变量名时,该变量的值将与false常数进行比较。如果都不匹配,表达式的结果为true。一个未定义的变量被认为是一个空字符串,因此表达式结果为false。

1
2
3
4
5
# Common pattern, often used with variables defined
# by commands such as option(enableSomething "...")
if(enableSomething)
	# ...
endif()

当表达式是一个带引号的字符串时,规则如下:

  • 在CMake 3.1或更高版本中,带引号的字符串总是为false,无论字符串的值如何(但这可以通过策略设置来重写)。
  • 在CMake 3.1之前,如果字符串的值与现有变量的名称相匹配,那么带引号的字符串就会替换为该变量的名称(未带引号),然后继续测试。

逻辑运算符

CMake支持常见的AND、OR和NOT逻辑运算符,以及括号用于控制优先级顺序。

1
2
3
4
5
6
7
# Logical operators
if(NOT expression)
if(expression1 AND expression2)
if(expression1 OR expression2)

# Example with parentheses
if(NOT (expression1 AND (expression2 OR expression3)))

比较测试

CMake将比较测试分为三个不同的类别:数字、字符串和版本号,但语法形式都遵循相同的模式:

1
if(value1 OPERATOR value2)

value1和value2,可以是变量名或(可能带引号)值。如果一个值与定义的变量名称相同,它将被视为一个变量。下表总结了三种类别比较测试支持的运算符:

数字 字符串 版本号
LESS STRLESS VERSION_LESS
GREATER STRGREATER VERSION_GREATER
EQUAL STREQUAL VERSION_EQUAL
LESS_EQUAL STRLESS_EQUAL VERSION_LESS_EQUAL
GREATER_EQUAL STRGREATER_EQUAL VERSION_GREATER_EQUAL

当进行数字比较时,如果一个操作数不是数字,CMake通常不会报错。根据数字和非数字的混合情况,表达式的结果可能是true或false。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Valid numeric expressions, all evaluating as true
if(2 GREATER 1)
if("23" EQUAL 23)
set(val 42)
if(${val} EQUAL 42)
if("${val}" EQUAL 42)

# Invalid expression that evaluates as true with at
# least some CMake versions. Do not rely on this behavior.
if("23a" EQUAL 23)

版本号格式为major[.minor[.patch[.tweak]],其中每个组成部分都是一个非负的整数。当比较两个版本号时,首先比较major部分。只有当major部分相等时,才会比较minor部分,依此类推。缺少的部分被视为零。以下表达式均为true:

1
2
3
4
5
if(1.2 VERSION_EQUAL 1.2.0)
if(1.2 VERSION_LESS 1.2.3)
if(1.2.3 VERSION_GREATER 1.2 )
if(2.0.1 VERSION_GREATER 1.9.7)
if(1.8.2 VERSION_LESS 2 )

字符串是按字母顺序比较的,对字符串的内容不做任何假设,但要注意变量/字符串替换的潜在情况。

CMake还支持正则表达式测试字符串:

1
if(value MATCHES regex)

value遵循前面讲的变量/字符串替换规则,并与regex正则表达式进行比较。如果值匹配,表达式为true。

圆括号可以用来捕获匹配值的部分内容,MATCHES会设置名称为CMAKE_MATCH_<n>的变量,其中<n>是要匹配的组,整个匹配的字符串被存储在0组中。

1
2
3
if("Hi from ${who}" MATCHES "Hi from (Fred|Barney).*")
	message("${CMAKE_MATCH_1} says hello")
endif()

文件系统测试

CMake还包括一组测试,用来查询文件系统。支持的表达式如下:

1
2
3
4
5
if(EXISTS pathToFileOrDir)
if(IS_DIRECTORY pathToDir)
if(IS_SYMLINK fileName)
if(IS_ABSOLUTE path)
if(file1 IS_NEWER_THAN file2)

如果其中一个文件缺失或两个文件时间戳相同,IS_NEWER_THAN操作符返回true。使用IS_NEWER_THAN时,应该提供绝对路径,因为相对路径的行为没有定义好。

另一点需要注意的是,与其他大多数if表达式不同,在没有${}的情况下文件系统操作符不会执行任何变量/字符串替换,不管是否有引号。

存在性测试

最后一类if表达式支持测试各种CMake实体是否存在。

1
2
3
4
5
if(DEFINED name)
if(COMMAND name)
if(POLICY name)
if(TARGET name)
if(TEST name) # Available from CMake 3.4 onward

如果在使用if命令的地方存在一个指定名称的实体,那么上述命令都将返回true。

DEFINED

如果一个指定名称的变量存在,则返回true。变量的值并不重要,只测试它是否被定义,也可以用来检查环境变量是否被定义。

COMMAND

测试指定名称的CMake命令、函数或宏是否存在。

POLICY

测试一个特定的策略是否被CMake知道。

TARGET

如果指定名称的CMake目标已由add_executable、add_library或add_custom_target命令之一定义,则返回true。该目标可以在任何目录下定义,只要在执行if测试时它是已知的。

TEST

如果指定名称的CMake测试已由add_test命令定义,则返回true。

最后一个存在性测试在CMake 3.5和更高版本中可用:

1
if(value IN_LIST listVar)

如果变量listVar包含指定的值,则表达式将返回true,其中value遵循通常的变量或字符串替换规则,但listVar必须是一个列表变量的名称。

循环

foreach

CMake提供了foreach命令,能够在一组元素或值上进行迭代。foreach有几种不同的形式,其中最基本的是:

1
2
3
foreach(loopVar arg1 arg2 ...)
    # ...
endforeach()

在上述形式中,对于每个argN值,loopVar被设置为该值并执行循环体。foreach更一般的形式如下:

1
2
3
foreach(loopVar IN [LISTS listVar1 ...] [ITEMS item1 ...])
    # ...
endforeach()

使用这种形式时,必须提供ITEMS和LISTS中的一个或两个。当两者都提供时,ITEMS必须出现在LISTS的后面。允许listVarN列表变量为空列表。

1
2
3
4
5
6
set(list1 A B)
set(list2)
set(foo WillNotBeShown)
foreach(loopVar IN LISTS list1 list2 ITEMS foo bar)
	message("Iteration for: ${loopVar}")
endforeach()

上面的输出是:

1
2
3
4
Iteration for: A
Iteration for: B
Iteration for: foo
Iteration for: bar

foreach()命令也支持数值范围迭代:

1
foreach(loopVar RANGE start stop [step])

loopVar被设置为[start, stop]范围内的每个值。如果提供了step选项,那么在每次迭代后,loopVar会加上step,当结果大于stop时,循环停止。

RANGE形式也可以只接受一个参数,像这样:

1
foreach(loopVar RANGE value)

这相当于foreach(loopVar RANGE 0 value)

while

CMake提供的另一个循环命令是while:

1
2
3
while(condition)
	# ...
endwhile()

中断循环

while和foreach循环都支持用break提前退出循环或用continue跳到下一个迭代的开始:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
foreach(outerVar IN ITEMS a b c)
    unset(s)
    foreach(innerVar IN ITEMS 1 2 3)
        # Stop inner loop once string s gets long
        list(APPEND s "${outerVar}${innerVar}")
        string(LENGTH s length)
        if(length GREATER 5)
        	break()
        endif()

        # Do no more processing if outer var is "b"
        if(outerVar STREQUAL "b")
        	continue()
        endif()
        message("Processing ${outerVar}-${innerVar}")
    endforeach()
    message("Accumulated list: ${s}")
endforeach()