【CMake】(13)流程控制

387 阅读9分钟

条件判断

基本语法

条件判断的基本语法如下:

if(<condition>)
  <commands>
elseif(<condition>)
  <commands>
else()
  <commands>
endif()
  • if(<condition>):检查条件是否满足。如果满足,则执行随后的命令直到遇到elseifelseendif
  • elseif(<condition>):可选。如果前面的ifelseif的条件不满足,将检查这里的条件。可以有多个elseif块。
  • else():可选。如果所有的ifelseif条件都不满足,则执行else块中的命令。
  • endif():结束条件判断块。

在CMake中,基本表达式用于if语句中,以决定是否执行特定的代码块。这些表达式可以是常量、变量或字符串,CMake会根据这些表达式的值来判断条件为真(True)或假(False)。

判定表达式

当表达式的值为以下之一时,条件被认为是真(True):

  • 数值 1:代表真。
  • 字符串 ON:明确表示启用或真。
  • 字符串 YES:同样表示肯定或真。
  • 字符串 TRUE:布尔真值。
  • 字符串 Y:简短的肯定回答,表示真。
  • 非零数值:在大多数编程语言中,非零值通常被解释为真。
  • 非空字符串:任何非空的字符串都被视为真,除了下面将要提到的特定假值字符串外。

当表达式的值为以下之一时,条件被认为是假(False):

  • 数值 0:代表假。
  • 字符串 OFF:明确表示禁用或假。
  • 字符串 NO:表示否定或假。
  • 字符串 FALSE:布尔假值。
  • 字符串 N:简短的否定回答,表示假。
  • 字符串 IGNORE:有时用于特定的设置中,解释为假。
  • 字符串 NOTFOUND:特别在查找库或程序时,如果未找到,这个值表示假。
  • 空字符串:表示没有值,解释为假。
# 示例:变量设置为非零值
set(MY_VAR 42)
if(MY_VAR)
  message("MY_VAR is true")
endif()

# 示例:变量设置为假值字符串
set(MY_VAR "FALSE")
if(NOT MY_VAR)
  message("MY_VAR is false")
endif()

# 示例:使用未定义的变量
if(UNDEFINED_VAR)
  message("This will not be printed")
else()
  message("UNDEFINED_VAR is considered false")
endif()

逻辑判断

逻辑操作符NOTANDOR允许你根据一个或多个条件来执行特定的代码块。这些操作符的行为与大多数编程语言中的逻辑操作符类似,非常直观。

NOT

NOT操作符用于取反一个条件的结果。如果原条件为真(True),则NOT后的结果为假(False);如果原条件为假(False),则NOT后的结果为真(True)。

set(VAR1 ON)

if(NOT VAR1)
  message("VAR1 is false")
else()
  message("VAR1 is true")
endif()

在这个例子中,因为VAR1被设置为ON(即真),NOT VAR1的结果为假,所以执行else分支,输出"VAR1 is true"。

AND

AND操作符用于检查两个或更多条件是否都为真。只有当所有条件都为真时,整个AND表达式的结果才为真(True);否则为假(False)。

set(VAR1 ON)
set(VAR2 OFF)

if(VAR1 AND VAR2)
  message("Both VAR1 and VAR2 are true")
else()
  message("At least one of VAR1 or VAR2 is false")
endif()

在这个例子中,因为VAR1为真而VAR2为假,整个条件表达式的结果为假,所以执行else分支。

OR

OR操作符用于检查两个或更多条件中至少有一个是否为真。如果至少有一个条件为真,整个OR表达式的结果就为真(True);只有当所有条件都为假时,结果才为假(False)。

set(VAR1 ON)
set(VAR2 OFF)

if(VAR1 OR VAR2)
  message("At least one of VAR1 or VAR2 is true")
else()
  message("Both VAR1 and VAR2 are false")
endif()

在这个例子中,因为至少有一个条件(VAR1)为真,所以整个条件表达式的结果为真,执行if分支。

比较

数值比较

数值比较用于比较两个变量或字符串代表的数值,包括:

  • LESS:检查左侧的数值是否小于右侧的数值。
  • GREATER:检查左侧的数值是否大于右侧的数值。
  • EQUAL:检查两个数值是否相等。
  • LESS_EQUAL:检查左侧的数值是否小于或等于右侧的数值。
  • GREATER_EQUAL:检查左侧的数值是否大于或等于右侧的数值。
set(NUM1 10)
set(NUM2 20)

if(NUM1 LESS NUM2)
  message("NUM1 is less than NUM2")
endif()

字符串比较

字符串比较根据字符串的字典顺序来比较两个变量或字符串的大小,包括:

  • STRLESS:如果左侧字符串在字典顺序上小于右侧,返回True。
  • STRGREATER:如果左侧字符串在字典顺序上大于右侧,返回True。
  • STREQUAL:如果两个字符串相等,返回True。
  • STRLESS_EQUAL:如果左侧字符串小于等于右侧,返回True。
  • STRGREATER_EQUAL:如果左侧字符串大于等于右侧,返回True。
set(STR1 "apple")
set(STR2 "banana")

if(STR1 STRLESS STR2)
  message("apple comes before banana")
endif()

文件判断

判断文件或目录是否存在

  • EXISTS 操作用于检查指定路径的文件或目录是否存在。如果路径存在,条件为真(True),否则为假(False)。
if(EXISTS "${PROJECT_SOURCE_DIR}/myfile.txt")
  message("myfile.txt exists.")
else()
  message("myfile.txt does not exist.")
endif()

判断是否为目录

  • IS_DIRECTORY 用于检查给定的路径是否是一个目录。这要求提供的路径是绝对路径。
if(IS_DIRECTORY "${PROJECT_SOURCE_DIR}/mydir")
  message("mydir is a directory.")
else()
  message("mydir is not a directory.")
endif()

判断是否为软链接

  • IS_SYMLINK 操作用于检查指定的文件名是否是一个软链接。这同样要求文件名对应的路径是绝对路径。
if(IS_SYMLINK "/path/to/mylink")
  message("mylink is a symlink.")
else()
  message("mylink is not a symlink.")
endif()

判断是否为绝对路径

  • IS_ABSOLUTE 用于检查给定的路径是否是绝对路径。在Linux上,绝对路径以根目录(/)开始;在Windows上,它以盘符开始(如C:/)。
if(IS_ABSOLUTE "/usr/local/bin")
  message("This is an absolute path.")
else()
  message("This is not an absolute path.")
endif()

其他

判断元素是否在列表中

CMake 3.3及更高版本支持IN_LIST查询,这允许开发者检查一个变量或字符串是否在一个给定的列表中。

set(MY_LIST apple banana cherry)
set(MY_ITEM apple)

if(MY_ITEM IN_LIST MY_LIST)
  message("${MY_ITEM} is in the list.")
else()
  message("${MY_ITEM} is not in the list.")
endif()

比较两个路径是否相等

CMake 3.24及更高版本引入了PATH_EQUAL,这对于比较两个路径是否相等非常有用,特别是在路径可能包含多余的分隔符时。PATH_EQUAL会在比较前标准化路径,从而忽略多余的分隔符。

if("/path/to//directory" PATH_EQUAL "/path/to/directory")
  message("Paths are equal.")
else()
  message("Paths are not equal.")
endif()

STREQUAL相比,PATH_EQUAL在处理路径时更加智能,能够识别并处理路径中的冗余分隔符。这在跨平台开发中特别有用,因为不同操作系统的路径分隔符习惯可能不同。

以下示例演示了PATH_EQUALSTREQUAL在处理包含多余分隔符的路径时的行为差异:

cmake_minimum_required(VERSION 3.26)
project(test)

if("/home//user///directory" PATH_EQUAL "/home/user/directory")
  message("Paths are equal using PATH_EQUAL.")
else()
  message("Paths are not equal using PATH_EQUAL.")
endif()

if("/home//user///directory" STREQUAL "/home/user/directory")
  message("Paths are equal using STREQUAL.")
else()
  message("Paths are not equal using STREQUAL.")
endif()

输出结果将展示PATH_EQUAL成功地忽略了路径中的多余分隔符,而STREQUAL则没有。

循环

有两种循环方式,分别是foreachwhile

foreach

使用foreach可以执行重复的任务,如设置变量、打印信息、或者根据列表中的每个项目执行特定的命令。

基本的foreach循环语法如下:

foreach(loop_var IN ITEMS item1 item2 ... itemN)
  # 执行的命令
endforeach()
  • loop_var是循环变量,在每次循环中,它被设置为当前项的值。
  • ITEMS后面跟着的是一系列要遍历的项目。

遍历数值范围

  1. 单一终止值:
foreach(loop_var RANGE stop)
  # 执行的命令
endforeach()
  • 这里loop_var从0遍历到stop,包括stop
  1. 指定起始、终止值(可选步长):
foreach(loop_var RANGE start stop [step])
  # 执行的命令
endforeach()
  • start:遍历开始的值。
  • stop:遍历结束的值。
  • step:(可选)遍历的步长,默认为1。

示例

当需要遍历一个从0开始到指定终止值的整数序列时,可以使用RANGE关键字。

foreach(item RANGE 10)
    message(STATUS "当前遍历的值为: ${item}")
endforeach()

这将输出从0到10的数值,包括10。

示例2

如果需要从特定的起始值开始遍历,到特定的终止值结束,并且还可以指定步长(默认为1),可以使用增强版的RANGE

foreach(item RANGE 10 30 2)
    message(STATUS "当前遍历的值为: ${item}")
endforeach()

这将从10开始,到30结束,步长为2,输出10, 12, 14, ..., 30。

遍历列表

foreach还可以遍历一个或多个列表,通过IN LISTSITEMS关键字。

  1. 遍历单一或多个列表:
foreach(loop_var IN LISTS list1 [list2 ...])
  # 执行的命令
endforeach()
  • IN LISTS后面可以指定一个或多个列表变量。
  1. 遍历一系列项目:
foreach(loop_var IN ITEMS item1 [item2 ...])
  # 执行的命令
endforeach()
  • IN ITEMS后面跟着要直接遍历的项目序列。

示例:遍历列表或多个列表

  • 使用IN LISTS直接遍历一个或多个列表变量:
set(WORD a b c d)
set(NAME ace sabo luffy)
foreach(item IN LISTS WORD NAME)
    message(STATUS "当前遍历的值为: ${item}")
endforeach()
  • 使用IN ITEMS遍历一个或多个通过变量展开的列表:
set(WORD a b c "d e f")
set(NAME ace sabo luffy)
foreach(item IN ITEMS ${WORD} ${NAME})
    message(STATUS "当前遍历的值为: ${item}")
endforeach()

遍历多个列表(CMake 3.17及以上)

foreach(loop_var1 loop_var2 ... IN ZIP_LISTS list1 list2 ...)
  # 执行的命令
endforeach()
  • IN ZIP_LISTS允许同时遍历多个列表,loop_var1loop_var2等变量分别对应每个列表中当前位置的元素。

示例:ZIP_LISTS

在CMake 3.17及以上版本中,foreach命令引入了IN ZIP_LISTS,这允许你同时遍历多个列表,并在每次迭代中从每个列表中取出相应位置的元素。

list(APPEND WORD hello world "hello world")
list(APPEND NAME ace sabo luffy zoro sanji)
foreach(item1 item2 IN ZIP_LISTS WORD NAME)
    message(STATUS "当前遍历的值为: item1 = ${item1}, item2=${item2}")
endforeach()

这种方式特别适用于需要同时处理多个相关列表的情况,如同时遍历文件名列表和对应的目标名列表。

while

foreach循环相比,while循环更加灵活,因为它允许你在每次循环结束时基于复杂的逻辑来更新循环条件。这使得while循环特别适合于处理那些在循环开始前不能确定迭代次数的情况。

基本语法:

while(<condition>)
    <commands>
endwhile()
  • <condition>:这是循环继续执行的条件。只要条件评估为真(即,非0或非空字符串,或特定的CMake真值,如ONTRUE等),循环就会继续。
  • <commands>:在每次循环迭代中执行的命令集。

示例:遍历列表并逐个移除元素

在你提供的示例中,while循环用于遍历一个列表,并在每次迭代中从列表的头部移除一个元素,直到列表为空:

cmake_minimum_required(VERSION 3.5)
project(test)

# 创建一个列表
set(NAME luffy sanji zoro nami robin)

# 获取列表长度
list(LENGTH NAME LEN)

# 循环直到列表为空
while(${LEN} GREATER 0)
    message(STATUS "names = ${NAME}")
    # 从列表头部移除一个元素
    list(POP_FRONT NAME)
    # 更新列表长度
    list(LENGTH NAME LEN)
endwhile()

每次循环迭代都会更新列表NAME,并重新计算其长度LEN。循环继续执行直到LEN为0,即列表中不再有元素。