华南虎视觉组主页华南虎视觉组主页
首页
项目
通用
教程
Gitea
首页
项目
通用
教程
Gitea
  • 现代 CMake 教程

    • 【01】安装与基本介绍
    • 【02】CMake 的文件分布
    • 【03】变量的设置与引用
    • 【04】运算符与条件、循环语句
      • 1. 条件控制:if
      • 2. 运算符
        • 2.1 一元运算符
        • 2.2 二元运算符
        • 2.3 逻辑运算符
      • 3. 循环控制:foreach
        • 3.1 用法
        • 3.2 其他写法
      • 4. 示例
        • 4.1 列出文件与文件夹
        • 4.2 用法示例
    • 【05】目标构建
    • 【06】变量参与 C++ 程序的编译
    • 【07】宏与函数
    • 【08】find_package 详解
    • 【09】生成器表达式
    • 【10】项目的导出与安装
    • 【11】项目实战
    • 【12】CTest
    • 【13】CPack

【04】运算符与条件、循环语句

1. 条件控制:if

条件控制的基本写法为:

if(aaa)
  # ...
elseif(bbb)
  # ...
else()
  # ...
endif()

并且能够支持嵌套条件控制,即

if(aaa)
  # ...
  if(ccc)
    # ...
  endif()
  # ...
else()
  # ...
endif()

2. 运算符

先给出以下例子

# 定义于 <opencv-path>/cmake/OpenCVModule.cmake 中的内容
if(" ${ARGV1}" STREQUAL " INTERNAL" OR " ${ARGV1}" STREQUAL " BINDINGS")
  set(OPENCV_MODULE_${the_module}_CLASS "${ARGV1}" CACHE INTERNAL "The category of the module")
  set(__ocv_argn__ ${ADD_MODULE_ARGN})
  list(REMOVE_AT __ocv_argn__ 0)
  ocv_add_dependencies(${the_module} ${__ocv_argn__})
  unset(__ocv_argn__)
else()
  set(OPENCV_MODULE_${the_module}_CLASS "PUBLIC" CACHE INTERNAL "The category of the module")
  ocv_add_dependencies(${the_module} ${ADD_MODULE_ARGN})
  if(BUILD_${the_module})
    set(OPENCV_MODULES_PUBLIC ${OPENCV_MODULES_PUBLIC} "${the_module}" CACHE INTERNAL "List of OpenCV modules marked for export")
  endif()
endif()

在 if() 的小括号中出现了 STREQUAL 以及 OR 的内容,这些属于运算符,我们有必要介绍一些能够产生 BOOL 类型变量的运算符,这些运算符也常用于条件控制中。

2.1 一元运算符

  • EXISTS:判断文件或者目录是否存在,存在时为真。需要提供绝对路径;如果文件或者目录是符号链接(例如软连接),则只有当链接的目标存在时返回真。格式为:EXISTS xxx,例如:

    # 定义于 <opencv-path>/cmake/OpenCVModule.cmake 中的内容
    if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/test")
    
  • IS_DIRECTORY:判断指定内容是否为文件夹,是则为真。需要提供绝对路径。格式为:IS_DIRECTOTY xxx,例如:

    if(IS_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/${m})
    
  • COMMAND:如果给定的名称是命令、宏或者函数这类可被调用的对象,则返回真。格式为:COMMAND xxx,例如:

    # 定义于 <opencv-path>/cmake/OpenCVModule.cmake 中的内容
    if(COMMAND ocv_get_module_external_sources)
    
  • DEFINED:如果给定的变量(普通变量、缓存变量或系统环境变量)存在,则返回真。格式为:DEFINED xxx,系统环境变量前要加ENV,即DEFINED ENV xxx。示例如下:

    # 定义于 <opencv-path>/cmake/OpenCVModule.cmake 中的内容
    if(DEFINED OPENCV_INITIAL_PASS)
    
  • TARGET:如果给定的名称是目标(包括二进制目标、伪目标,可参考【05】目标构建),则返回真。格式为:TARGET xxx,例如:

    # 定义于 <opencv-path>/cmake/OpenCVGenABI.cmake 中的内容
    if(TARGET opencv_${mod}) # opencv_world
    

2.2 二元运算符

  • EQUAL:左边两个字符串或者变量相等时为真。
  • STREQUAL:左边与右边的字典顺序相等时为真。
  • MATCHES:按照正则表达式去匹配,左边是待匹配的值,右边是正则表达式,能匹配为时为真。例如:
    # 定义于 <opencv-path>/cmake/OpenCVModule.cmake 中的内容
    # m 为某个 foreach 中的元素
    if(NOT m MATCHES "^opencv_")
    

2.3 逻辑运算符

与其他语言一致,包含与、或、非:AND、OR、NOT

if(BUILD_opencv_world AND m STREQUAL "opencv_world"
    OR NOT BUILD_opencv_world
    OR NOT OPENCV_MODULE_${m}_IS_PART_OF_WORLD)

逻辑操作符的结合优先级:

NOT>AND>OR

提示

运算符结合顺序为:一元运算符 > 二元运算符 > 逻辑运算符

3. 循环控制:foreach

这里介绍最常用的foreach,while这里不做介绍

3.1 用法

先给出两个例子:

# 以下均为定义于 <opencv-path>/cmake/OpenCVModule.cmake 中的内容
# Example 1
foreach(subdir ${subdirs})
  if(EXISTS "${path}/${subdir}/CMakeLists.txt")
    list(APPEND paths "${path}/${subdir}")
    list(APPEND names "${subdir}")
  endif()
endforeach()

# Example 2
foreach(mod ${OPENCV_MODULES_BUILD} ${OPENCV_MODULES_DISABLED_USER} ${OPENCV_MODULES_DISABLED_AUTO} ${OPENCV_MODULES_DISABLED_FORCE})
  if(HAVE_${mod})
    unset(HAVE_${mod} CACHE)
  endif()
  unset(OPENCV_MODULE_${mod}_DEPS CACHE)
  unset(OPENCV_MODULE_${mod}_DEPS_EXT CACHE)
  # 省略一系列 unset
endforeach()

可以得出,遍历循环控制的基本写法为:

foreach(每个元素 ${列表1} ${列表2} ...)
  循环体
endforeach()

对于以上两个例子 Example 1 和 Example 2,有以下注意的地方:

  • Example 1

    1. ${subdirs} 不可写成 "${subdirs}",我们假设此处 ${subdirs} 内容有:a、b、c,那么遍历的时候 subdir 则分别指代a、b、c。而如果写成 "${subdirs}",展开则表示 "a;b;c",那么 subdir 则指代 a;b;c 这个变量。

    2. subdir 是个普通变量,因此在访问(取值)时也需要用 ${} 的写法。

  • Example 2

    1. 共有 4 个待遍历的列表,遍历则会将这 4 个列表中的所有内容依次访问。

    2. mod为一个变量,不例外的也需要用${}来访问。同时我们还能看到这种写法:

      unset(OPENCV_MODULE_${mod}_DEPS CACHE)
      

      ${mod} 用在了一个变量的中间,假设此时 ${mod} 指的是 aa,那么这个变量则表示为:OPENCV_MODULE_aa_DEPS

注意

条件控制结束标志endif()和循环控制结束标志endforeach()括号中的内容可以不写,一旦写,就必须跟if()和foreach()括号中的内容一致

3.2 其他写法

foreach 还有其他写法,例如对于

foreach(m ${members})
  # ...
endforeach()

有更直观的写法,我们可以写成

foreach(m IN LIST members)
  # ...
endforeach()

4. 示例

如果我们想遍历当前目录下的所有文件夹要怎么办?在终端中我们可以使用ls来列出当前路径下的所有内容,包括文件夹,当然也包括了文件,在 CMake 中我们要如何调用命令行呢?

4.1 列出文件与文件夹

4.1.1 执行子进程:execute_process

先列出官网给出的命令原型:

execute_process(COMMAND <cmd1> [<arguments>]
                [COMMAND <cmd2> [<arguments>]]...
                [WORKING_DIRECTORY <directory>]
                [TIMEOUT <seconds>]
                [RESULT_VARIABLE <variable>]
                [RESULTS_VARIABLE <variable>]
                [OUTPUT_VARIABLE <variable>]
                [ERROR_VARIABLE <variable>]
                [INPUT_FILE <file>]
                [OUTPUT_FILE <file>]
                [ERROR_FILE <file>]
                [OUTPUT_QUIET]
                [ERROR_QUIET]
                [COMMAND_ECHO <where>]
                [OUTPUT_STRIP_TRAILING_WHITESPACE]
                [ERROR_STRIP_TRAILING_WHITESPACE]
                [ENCODING <name>]
                [ECHO_OUTPUT_VARIABLE]
                [ECHO_ERROR_VARIABLE]
                [COMMAND_ERROR_IS_FATAL <ANY|LAST>])

这里列出常用的几个选项:

  • COMMAND

    • 用于指定一个子进程命令行。

    • CMake直接使用操作系统 API 执行子进程。所有参数都被逐字传递给子进程。没有使用中间shell,所以像>这样的shell操作符被视为普通参数。

    • 如果需要连续执行多个命令,请使用多个execute_process()调用,并使用单个COMMAND参数。

  • OUTPUT_VARIABLE

    • 变量名将分别用标准输出管道的内容设置。
  • ERROR_VARIABLE

    • 变量名将分别用标准错误管道的内容设置。

因此,列出当前目录下所有文件夹,就可以写为

execute_process(COMMAND ls ${CMAKE_CURRENT_LIST_DIR} OUTPUT_VARIABLE subs)

其中,输出的变量为subs,这个操作仅能在支持ls命令的平台上使用,一般是 Linux。

4.1.2 使用 file 操作

为实现同样效果,我们可以使用更加通用的 file 命令

set(cur_path "${CMAKE_CURRENT_LIST_DIR}")
file(GLOB subs RELATIVE "${cur_path}" "${cur_path}/*")

其中,输出的变量为 subs

4.2 用法示例

例如,列出当前目录下所有的文件夹,并添加至 dirs 中

set(dirs)
set(cur_path "${CMAKE_CURRENT_LIST_DIR}")
file(GLOB subs RELATIVE "${cur_path}" "${cur_path}/*")

foreach(_sub ${subs})
  if(IS_DIRECTORY ${_sub})
    list(APPEND dirs ${_sub})
  endif()
foreach()
Prev
【03】变量的设置与引用
Next
【05】目标构建