基本签名和模块模式

find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
             [REQUIRED] [[COMPONENTS] [components...]]
             [OPTIONAL_COMPONENTS components...]
             [NO_POLICY_SCOPE])cs

查找并从外部项目加载设置。 <PackageName>_FOUND 将设置为指示是否找到该软件包。找到软件包后,将通过软件包本身记录的变量和“ imported target”提供特定于软件包的信息。该 QUIET选项禁用信息性消息,包括指示信息性消息的信息,如果未找到则无法找到REQUIREDREQUIRED 如果找不到软件包,该选项将停止处理并显示一条错误消息。

COMPONENTS选件后(或REQUIRED选件后,如果有的话)可以列出所需组件的特定于包装的列表 。后面可能会列出其他可选组件 OPTIONAL_COMPONENTS。可用组件及其对是否认为要查找包的影响由目标包定义。

[version]参数要求找到的软件包应与之兼容的版本(格式为major[.minor[.patch[.tweak]]])。该 EXACT选项要求版本完全匹配。如果未[version]对find-module内部的递归调用提供任何 和/或组件列表,则将从外部调用(包括的EXACT标志 [version])自动转发相应的参数。当前仅在逐个软件包的基础上提供版本支持

两种模式

该命令有两种搜索包的模式:“模块”模式和“配置”模式。上面的签名选择模块模式。如果未找到模块,该命令将退回到配置模式,如下所述。如果提供了此MODULE选项,则禁用此回退。

如何区分这两种模式

也就是说,只有这3种情况下才是Config模式:

find_package()中指定CONFIG关键字
find_package()中指定NO_MODULE关键字
find_package()中使用了不在"basic signature"(也就是Module模式下所有支持的配置)关键字
换句话说,只要我不指定"CONFIG",不指定“NO_MODULE",也不使用"full signature"中的关键字,那我就是在Module模式。排查find_package()的第一步,应当判断它是Module模式还是Config模式。


指定关键字MODULE指定只用module模式

img

模块模式

在模块模式下,CMake搜索名为的文件Find<PackageName>.cmake。首先在CMAKE_MODULE_PATH,然后在CMake安装提供的“ 查找模块”中。如果找到该文件,则由CMake读取和处理。它负责查找软件包,检查版本并生成任何需要的消息。某些查找模块仅提供有限的版本支持或不提供版本支持。检查模块文档

find_package( [version] [EXACT] [QUIET] [MODULE]

         [REQUIRED] [[COMPONENTS] [components...]]
         [OPTIONAL_COMPONENTS components...]
         [NO_POLICY_SCOPE])

查找模块

“查找模块”是Find<PackageName>.cmake要由find_package()为调用时的命令<PackageName>

查找模块的主要任务是确定系统上是否存在软件包,设置<PackageName>_FOUND变量以反映此问题,并提供使用该软件包所需的任何变量,宏和导入的目标。在上游库不提供配置文件包的情况下,查找模块很有用 。

传统方法是对所有内容使用变量,包括库和可执行文件:请参见下面的“ 标准变量名称”部分。这是CMake提供的大多数现有查找模块的功能。

更现代的方法是通过提供导入的target,使其行为尽可能像 配置文件包文件。这具有向消费者传播传递使用要求的优点。

无论哪种情况(甚至提供变量和导入的目标),find模块都应与具有相同名称的旧版本提供向后兼容性。

从文件或模块加载并运行CMake代码。

include(<file|module> [OPTIONAL] [RESULT_VARIABLE <var>]
                      [NO_POLICY_SCOPE])

从给定的文件加载并运行CMake代码。变量读写访问调用者的范围(动态作用域)。如果OPTIONAL 存在,则如果文件不存在,则不会引发任何错误。如果 RESULT_VARIABLE给出,则变量<var>将设置为已包含的完整文件名,或者NOTFOUND失败。

如果指定了模块而不是文件,<modulename>.cmake则首先搜索名称为的文件 CMAKE_MODULE_PATH,然后在CMake模块目录中。对此有一个例外:如果调用的文件include()位于CMake内置模块目录中,则首先搜索CMake内置模块目录,然后 CMAKE_MODULE_PATH之后

关键字解释

versionEXACT: 都是可选的,version指定的是版本,如果指定就必须检查找到的包的版本是否和version兼容。如果指定EXACT则表示必须完全匹配的版本而不是兼容版本就可以。

QUIET 可选字段,表示如果查找失败,不会在屏幕进行输出(但是如果指定了REQUIRED字段,则QUIET无效,仍然会输出查找失败提示语)。

MODULE 可选字段。前面提到说“如果Module模式查找失败则回退到Config模式进行查找”,但是假如设定了MODULE选项,那么就只在Module模式查找,如果Module模式下查找失败并不回落到Config模式查找。

REQUIRED可选字段。表示一定要找到包,找不到的话就立即停掉整个cmake。而如果不指定REQUIRED则cmake会继续执行。

COMPONENTScomponents:可选字段,表示查找的包中必须要找到的组件(components),如果有任何一个找不到就算失败,类似于REQUIRED,导致cmake停止执行。

OPTIONAL_COMPONENTScomponents:可选的模块,找不到也不会让cmake停止执

Module模式查找顺序

Module模式下是要查找到名为Find<PackageName>.cmake的文件。

先在CMAKE_MODULE_PATH变量对应的路径中查找。如果路径为空,或者路径中查找失败,则在cmake module directory(cmake安装时的Modules目录,比如/usr/local/share/cmake/Modules)查找。

配置模式

如果MODULE以上签名中未指定该选项,则CMake首先使用“模块”模式搜索软件包。然后,如果找不到该软件包,它将使用Config模式再次搜索。用户可以设置变量CMAKE_FIND_PACKAGE_PREFER_CONFIGTRUE直接CMake的第一搜索回落至模块模式之前使用配置模式。

find_package(<PackageName> [version] [EXACT] [QUIET]
[REQUIRED] [[COMPONENTS] [components...]]
[CONFIG|NO_MODULE]
[NO_POLICY_SCOPE]
[NAMES name1 [name2 ...]]
[CONFIGS config1 [config2 ...]]
[HINTS path1 [path2 ... ]]
[PATHS path1 [path2 ... ]]
[PATH_SUFFIXES suffix1 [suffix2 ...]]
[NO_DEFAULT_PATH]
[NO_PACKAGE_ROOT_PATH]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[NO_SYSTEM_ENVIRONMENT_PATH]
[NO_CMAKE_PACKAGE_REGISTRY]
[NO_CMAKE_BUILDS_PATH] # Deprecated; does nothing.
[NO_CMAKE_SYSTEM_PATH]
[NO_CMAKE_SYSTEM_PACKAGE_REGISTRY]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH])

Config模式搜索尝试查找要查找的软件包提供的配置文件。<PackageName>_DIR创建一个名为的缓存条目来保存包含文件的目录。默认情况下,该命令搜索名称为的软件包<PackageName>。如果NAMES给定选项,则使用其后的名称代替<PackageName>该命令搜索名为<PackageName>Config.cmake<lower-case-package-name>-config.cmake指定的每个名称的文件。可以使用该CONFIGS选项替换一组可能的配置文件名。搜索步骤在下面指定。找到后,CMake将读取并处理配置文件。由于文件是由包提供的,因此它已经知道包内容的位置。配置文件的完整路径存储在cmake变量中

<PackageName>_CONFIG

config下查找顺序

CMake为该软件包构造了一组可能的安装前缀。在每个前缀下的几个目录中搜索配置文件。下表显示了搜索的目录。每个条目均用于遵循Windows(W),UNIX(U)或Apple(A)约定的安装树:

<prefix>/                                                       (W)
<prefix>/(cmake|CMake)/                                         (W)
<prefix>/<name>*/                                               (W)
<prefix>/<name>*/(cmake|CMake)/                                 (W)
<prefix>/(lib/<arch>|lib*|share)/cmake/<name>*/                 (U)
<prefix>/(lib/<arch>|lib*|share)/<name>*/                       (U)
<prefix>/(lib/<arch>|lib*|share)/<name>*/(cmake|CMake)/         (U)
<prefix>/<name>*/(lib/<arch>|lib*|share)/cmake/<name>*/         (W/U)
<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/               (W/U)
<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/(cmake|CMake)/ (W/U)

这组目录旨在与在其安装树中提供配置文件的项目一起工作。上面标有(W)的目录旨在用于Windows上的安装,其中前缀可能指向应用程序的安装目录的顶部。带有(U)的标记适用于在前缀由多个软件包共享的UNIX平台上安装。这仅仅是一个约定,==因此仍在所有平台上搜索所有(W)和(U)目录。==标有(A)的目录旨在在Apple平台上安装。

在apple系统中cmake的 CMAKE_FIND_FRAMEWORKCMAKE_FIND_APPBUNDLE 变量确定优先顺序。

注意这是后缀

在所有情况下,<name>均视为不区分大小写,并且对应于指定的任何名称(<package>或给出的名称NAMES)。lib/<arch>如果CMAKE_LIBRARY_ARCHITECTURE设置了变量,则启用 lib/<arch>。如果PATH_SUFFIXES 指定了后缀,则将后缀一一追加到每个(W)或(U)目录条目中。

使用以下步骤构造安装前缀集。如果NO_DEFAULT_PATH指定,NO_*则启用所有选项。

注意下面是构造前缀

也就是说配置了前缀,他们就会在前缀后面的一系列指定目录进行搜索,如果不想还可以加入后缀

顺序

  1. 在特定于 cmake 的缓存变量中指定的搜索路径。在命令行中使用-DVAR = value。如果传递 NO_CMAKE_PATH,可以跳过这个步骤:(这个和命令行结合使用)

    CMAKE_PREFIX_PATH
    CMAKE_FRAMEWORK_PATH
    CMAKE_APPBUNDLE_PATH
    
  2. 在特定于 cmake 的环境变量中指定的搜索路径。这些是用于在用户的 shell 配置中设置的。如果传递 NO_CMAKE_ENVIRONMENT_PATH,则可以跳过:(这个才是我们的搜索路径)

    <package>_DIR
    CMAKE_PREFIX_PATH
    CMAKE_FRAMEWORK_PATH
    CMAKE_APPBUNDLE_PATH
    
  3. 由“提示”选项指定的搜索路径。这些路径应该是由系统自检计算出来的,比如由已经找到的另一个项的位置提供的提示。硬编码的猜测值应该用 PATHS 选项指定。

  4. 搜索标准的系统环境变量。如果传递的是 NO_SYSTEM_ENVIRONMENT_PATH,则可以跳过这一步。以/bin 或/sbin 结尾的路径条目自动转换为它们的父目录:

    PATH
    
  5. 搜索项目最近在 cmake-gui (1)中配置的生成树。如果传递 no_cmake _ builds _ path,则可以跳过此操作。当用户正在一个接一个地构建多个依赖项目时,就需要使用它。(此步骤仅在 Windows. 上实现)

  6. 存储在 CMake 用户包注册表中的搜索路径。如果传递NO_CMAKE_PACKAGE_REGISTRY,或者设定CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY为true则可以跳过此操作。有关用户包注册表的详细信息,请参阅 cmake-packages (7)手册。

  7. 搜索当前系统在 Platform 文件中定义的 cmake 变量。如果传递NO_CMAKE_SYSTEM_PATH,可以跳过这个步骤:

    CMAKE_SYSTEM_PREFIX_PATH
    CMAKE_SYSTEM_FRAMEWORK_PATH
    CMAKE_SYSTEM_APPBUNDLE_PATH
    
  8. 搜索存储在 CMake 系统包注册表中的路径。如果传递 NO_CMAKE_SYSTEM_PACKAGE_REGISTRY,则可以跳过此操作。有关系统包注册表的详细信息,请参阅 cmake-packages (7)手册。

  9. 由 PATHS 选项指定的搜索路径。这些通常是硬编码的猜测。

模式介绍

  • Module模式:搜索CMAKE_MODULE_PATH指定路径下的FindXXX.cmake文件,执行该文件从而找到XXX库。其中,具体查找库并给XXX_INCLUDE_DIRSXXX_LIBRARIES两个变量赋值的操作由FindXXX.cmake模块完成(先搜索当前项目里面的Module文件夹里面提供的FindXXX.cmake,然后再搜索系统路径/usr/local/share/cmake-x.y/Modules/FindXXX.cmake
  • Config模式:搜索XXX_DIR指定路径下的XXXConfig.cmake文件,执行该文件从而找到XXX库。其中具体查找库并给XXX_INCLUDE_DIRSXXX_LIBRARIES两个变量赋值的操作由XXXConfig.cmake模块完成。
  • 对于可能没有***.cmake和***Config.cmake的库文件,可以直接找到其头文件和库文件所在文件夹,直接进行路径赋值:
set(LAPACK_DIR /usr/local/lib/) 
set(LAPACK_INCLUDE_DIRS /usr/local/include) 
set(LAPACK_LIBRARIES /usr/local/lib)

结果

**不管使用哪一种模式,只要找到*.cmake*.cmake里面都会定义下面这些变量:

 <NAME>_FOUND
 <NAME>_INCLUDE_DIRS or <NAME>_INCLUDES
 <NAME>_LIBRARIES or <NAME>_LIBRARIES or <NAME>_LIBS
 <NAME>_DEFINITIONS
cmake_minimum_required(VERSION 3.1.0)

project(realsense)

set( CMAKE_CXX_COMPILER "g++" )
set( CMAKE_BUILD_TYPE "Release" )
set( CMAKE_CXX_FLAGS "-std=c++11 -O3" )
# Enable C++11
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
# set(CMAKE_LIBRARY_ARCHITECTURE TRUE)
# set(OpenCV_DIR "/usr/local/opencv4.4.0/lib/cmake/opencv")

set(CMAKE_PREFIX_PATH  "/usr/local/opencv4.4.0/lib/cmake/opencv4")
# set(PATH "/usr/local")

# set(OpenCV_ROOT "/usr/local/opencv4.4.0/lib")
# FIND_PACKAGE(OpenCV  CONFIG PATHS /usr/local/opencv4.4.0/lib/cmake/opencv4 NO_DEFAULT_PATH )
find_package(OpenCV CONFIG REQUIRED)
FIND_PACKAGE(realsense2 REQUIRED)

 

message(${OpenCV_INCLUDE_DIRS} " kk" ${OpenCV_CONFIG})
include_directories( ${OpenCV_INCLUDE_DIRS} )
include_directories( ${realsense2_INCLUDE_DIRS})
add_executable(main test.cc)

target_link_libraries( main ${OpenCV_LIBS} ${realsense2_LIBRARY})

/usr/local/opencv4.4.0/include/opencv4 kk/usr/local/opencv4.4.0/lib/cmake/opencv4/OpenCVConfig.cmake
-- Configuring done
-- Generating done
-- Build files have been written to: /home/zhongsy/Desktop/test/test_opencv/build
[100%] Built target main

可以参考博客

link1

link2

link3

CMake中的变量

命令调用

cmake的输入文件采用CMake的语法书写,由一条条命令调用组成。cmake提供许多命令,开发者通过这些命令描述代码项目的构建过程,CMakeLists.txt文件的内容就是对这些命令的调用。

命令调用格式为:

command_name(arg0 arg1 arg2 ...)

其中,command_name是调用的命令名称,命令的名称是大小写无关的。括号内则是要传递给这个命令的参数,当存在多个参数时,使用空格隔开。如果参数较多,也可以使用换行符分隔,但左括号必须与命令名在同一行。

command_name(
    arg0 arg1 arg2
    arg3 arg4 arg5
    arg6 arg7 arg8
)

调用命令时,即使没有参数,小括号也不可省略。

注释

cmake语法中,注释以#开头,一直到这一行的结尾,例如:

# 注释注释注释
add_executable(app main.cpp) # 注释注释注释

message打印命令

在后面的解释中,经常会用到message命令作为例子。message命令可以接收任意个参数,将参数内容从左到右拼接后输出到控制台上。例如:

message(hello)   # 输出 hello
message(A B C D) # 输出 ABCD

**注意:**输出的内容是直接拼接的,并没有添加空格。

参数类型

cmake中,传递给命令的参数可以有三种类型:普通参数引号参数括号参数。cmake的所有命令都支持这三种参数。

普通参数 Unquoted Argument

普通参数是最常见的参数类型,例如:

message(hello)
# hello 为普通参数

普通参数不能包含空格,双引号",括号(),和井号#,除非使用反斜杠\进行转义,比如:

message(abc\ \"\(\)\#)
# 输出 abc "()#

引号参数 Quoted Argument

被一对双引号包含的参数称为引号参数。例如:

message("hello, world!")
# "hello, world!" 为引号参数

双引号内的内容是参数的值,双引号本身不属于参数值的一部分,不会被传递给调用的命令。引号参数内可以包含任意字符,包括空格和换行,但如果参数内容本身又含有双引号,需要使用反斜杠\进行转义。参数内也可以使用\n \r \t表示换行和制表符,或\\表示一个反斜杠本身。

message("First line.\nSecond line, and some special characters ( ) # : ; \\
Third line, and a double-quote \" .")

输出为:

First line.
Second line, and some special characters ( ) # : ; \
Third line, and a double-quote " .

括号参数 Bracket Argument

括号参数以两个连续的左方括号开始,以两个连续的右方括号结束,有些类似Lua语言中多行注释的语法。例如:

message([[hello]])
# [[hello]] 为括号参数

开始和结束的两个方括号之间也可以加入任意个等号,但前后的数量必须相同,例如:

message([==[hello]==])

括号参数内可以包含任意字符,包括空格和换行。开始和结束符不属于参数值的一部分,不会被传递给调用的命令。括号参数的值就是字面值,不会处理参数内的任何转义字符。另外,如果开始符后面紧跟一个换行,这个换行也会被忽略。例如:

message([[One Line.]])
message([=[
First Line. hello\n\tcmake
Second Line.]=])

输出为:

One Line.
First Line. hello\n\tcmake
Second Line.

如果参数内容本身包含右侧的结束符,需要调整两侧等号的数量以避开,例如:

message([==[abc]=]def]==])
# 输出 abc]=]def

**注意:**只有3.0以及更高版本的cmake支持括号参数,之前的版本会将其当作普通参数进行处理。

变量

CMake本身提供了变量的概念,但与一般语言中变量的功能相差甚远,更类似于字符串替换宏。

定义变量

变量的定义和赋值需要使用set命令,set命令有多种调用形式,最常用的一种是传递两个参数,第一个参数是变量名,第二个参数是变量的值。例如:

set(ABC hello)

表示定义一个名为ABC的变量,值为hello。如果当前已经存在重名的变量,则会直接覆盖它的值。

CMake中的变量名是大小写敏感的,ABCabc是完全不同的两个变量。理论上,变量名可以包含任意非空字符,但在实际使用时,强烈建议采用大部分语言的做法,即仅包含字母数字和下划线,不要起一些复杂蹩脚的名字。另外,CMake预定义了许多变量,用于控制工程构建过程,大部分以CMAKE_CTEST_PROJECT_开头,还有一些表示系统平台的变量,比如WIN32MSVCUNIX等,需要注意一下自定义的变量名不要和这些预定义变量产生冲突。CMake的文档中有列举所有预定义的变量,和它们的作用。

如果要删除一个变量的定义,可以使用unset命令并传入变量名。另外,使用set命令并且只传入变量名也可以删除变量。例如,以下两条命令都表示删除变量ABC。

unset(ABC)
set(ABC)

引用变量

之所以使用变量,就是为了避免填写重复的参数,当某些相同的参数需要被传递给很多不同的命令时,就可以用一个变量替换这些重复的参数。CMake语法中,使用${ }来引用一个变量的值,大括号中填入变量名。例如:

set(ABC hello)
message(${ABC}) # 引用变量ABC的值,输出结果为hello

这种引用基本可以当作是类似于C语言中宏定义替换。变量的引用不必单独作为一个参数出现,而是可以出现在普通参数和引号参数内,也就是说,可以将变量的引用和其他内容拼接在一起。例如:

set(ABC 456)
message(123${ABC}789)
message("123 ${ABC} 789")

输出:

123456789
123 456 789

但如果是括号参数内包括${ABC},则不会被处理,因为括号参数始终保持字面值。在普通参数和引号参数内,如果不希望${ }表示变量引用,可以进行转义处理。例如:

message([[123${ABC}789]])
message(123\${ABC}789)
# 以上两条命令输出都为 123${ABC}789

当某个变量未定义,或被定义为空值时,对其引用的结果为空,可能会引起一些奇怪的错误。例如:

set(A1 "")
set(A2 [[]])
message(${A0})   # error, A0未定义
message(${A1})   # error,A1为空
message(${A2})   # error,A2为空
message("${A0}") # ok,输出空行

上例中,变量A0未定义,所以${A0}为空,因此message(${A0})就等同于message(),没有传递任何参数,但是message命令至少需要一个参数,所以最终导致运行错误。变量A1和A2被定义为空,同理也会导致错误。而message("${A0}")能够正常执行,因为引用后变成了message(""),实际传递了一个空的引号参数,结果为打印输出一个空行。通过这个例子也可以看出来变量引用就是字符串的替换。

一般情况下,如果需要通过message命令观察某个变量的值,最好使用引号参数的形式,比如message("${A0}")。这样一来即使变量A0没有定义或者为空,也不会引发错误,而是输出一个空行。

变量的引用也可以嵌套。例如:

set(INDEX 2)
set(DATA2 000)
message("${DATA${INDEX}}")
# 输出 000

嵌套引用时,由内到外依次引用。上例中首先引用变量INDEX的值,参数变为"${DATA2}",然后引用变量DATA2的值,最终参数为"000"

列表变量

cmake提供存储列表的功能,可以将多个值保存在一个变量中,比如常用于保存多个源文件的文件名。定义一个列表同样需要使用set命令,但不同于一般变量的定义,定义列表时除第一个参数表示变量名外,后面可以添加任意个参数,每个参数都表示一个列表元素。例如:

set(SOURCES a.cpp b.cpp c.cpp) 
# 变量SOURCE包含三个元素
add_executable(app ${SOURCES})
# 等同于 add_executable(app a.cpp b.cpp c.cpp)

实际上,cmake中所有变量的类型都是字符串,并没有传统意义上的列表类型变量,而是通过以下两个特殊机制来实现类似“列表”变量的功能。

  • 定义变量时,如果提供了多个值,则使用分号作为分隔符,将它们拼接起来之后作为变量的值。
  • 传递参数时,如果一个普通参数中含有分号,则使用分号作为分隔符,将这个参数拆分成多个普通参数

上例中,set命令执行过后,变量SOURCE的值实际为a.cpp;b.cpp;c.cpp。set命令使用分号将所有参数拼接起来作为SOURCE的值。add_executable命令执行时,引用变量后变为add_executable(app a.cpp;b.cpp;c.cpp)然后按照分号进行拆分,最终等同于add_executable(app a.cpp b.cpp c.cpp)

定义列表变量时的拼接,是直接拼接的参数的值,不管参数本身中是否已经存在分号,也不管参数是什么类型。例如:

set(ABC a b c;d "e;f" [[g;h]])
# 变量ABC的值为 a;b;c;d;e;f;g;h

只有含有分号的普通参数才会被拆分,引号参数和括号参数即便含有分号也不会被拆分。如果不希望普通参数被拆分,可以使用反斜杠\对分号进行转义。例如:

message(a;b;c)     # 输出 abc
message(a\;b\;c)   # 输出 a;b;c
message("a;b;c")   # 输出 a;b;c
message([[a;b;c]]) # 输出 a;b;c

set命令同其他命令一样,如果参数中含有变量引用,也需要先处理变量引用,利用这一点,可以追加列表元素。例如:

set(ABC a b)
set(ABC ${ABC} c d)
#变量ABC的值为a;b;c;d

环境变量

CMake中也可以引用和设置环境变量。

环境变量的引用使用$ENV{VAR},例如:

#打印环境变量PATH的值
message($ENV{PATH})

设置环境变量同样使用set命令,并使用ENV{...}包含变量名,表示设置的是环境变量,例如:

set(ENV{PATH} $ENV{PATH}:/opt/bin)

条件判断

通过cmake配置代码项目时,可能需要一些条件判断来进行差异处理,比如在不同的系统平台上使用不同的参数设置。CMake中使用if/elseif/else/endif命令进行条件判断,形式如下。

if(expression)
  #...
elseif(expression2)
  #...
else()
  #...
endif()

if中的表达式有很多种形式,个人感觉设计的比较反人类,刚开始接触会很不适应。下面介绍一些比较常用的形式,其他形式可以自己查阅文档了解。

变量值判断

if(<argument>)

接收一个参数,根据参数值进行判断,判断采用如下规则:

首先进行常量判断(忽略大小写):
\1. 如果参数值为1, ON, YES, TRUE, Y, 或者任意非零的数字,则判断结果为真。
\2. 如果参数值为0, OFF, NO, FALSE, N, IGNORE, NOTFOUND, 空字符串, 或者以-NOTFOUND 结尾的值,则判断结果为假。

如果参数值不属于上面这些常量值中的任何一种,则该参数会被认为是变量名,进行变量判断(变量名大小写敏感):
\3. 如果该变量未定义,判断结果为假。
\4. 如果该变量的值为0, OFF, NO, FALSE, N, IGNORE, NOTFOUND, 空字符串, 或者以-NOTFOUND 结尾的值,则判断结果为假,否则为真。

规则1和2容易理解,但如果和变量以及变量引用结合,就容易绕晕,看几个例子。

set(A NO)
set(B A)

if(A) # false
endif()

if(${A}) # false
endif()

if(B) # true
endif()

if(${B}) # false
endif()

if(A),因为参数A不属于规则1和2中可以被直接判断的常量,所以A被当作变量名,又因为变量A的值为NO,所以根据规则4,最终判断结果为false;

if(${A}),if命令和其他命令一样,如果参数中含有变量引用,先处理变量引用。此例引用变量A后相当于if(NO),根据规则2,参数NO属于能够直接判断的常量,结果为false;

if(B),和if(A)类似,参数B不属于规则1和2中可以被直接判断的常量,所以B被当作变量名,但因为变量B的值为A,根据规则4,不符合能够判为false的条件,所以判断结果为true;

if(${B}),因为变量B的值为A,处理变量引用后相当于if(A),参数A被当作变量名,最终结果和if(A)相同,为false。

大小判断

if(<variable|string> <op> <variable|string>)

根据的值不同,有两种判断:

  • 当为LESS, GREATER, EQUAL, LESS_EQUAL, GREATER_EQUAL时,按数值判断大小关系。
  • 当为STRLESS, STRGREATER, STREQUAL, STRLESS_EQUAL, STRGREATER_EQUAL时,按字符串判断大小关系。

执行时首先检查op前后的参数是否为已定义的变量名,如果是,则按变量的值进行处理,否则按参数字面值处理。例如:

set(A 1)
set(B 2)
if(A LESS B) # true
endif()

if(A LESS B)中A和B都是已知的变量名,所以按它们的值进行判断,结果为真。但如果变量A和B都未定义,则判断结果为假,因为按参数字面值处理时,A和B不是数字。

文件判断

if(EXISTS <path-to-file-or-directory>)

第一个参数为固定的EXISTS关键字,第二个参数可以是文件或目录的全路径,用于判断文件或目录是否存在,存在为真,否则为假。

与或非

if命令中的表达式也可以使用AND,OR,NOT,和括号进行组合,形成复杂的表达式。例如:

if(NOT ((1 LESS 2) AND (3 LESS 4)))

循环

foreach命令

foreach/endforeach命令用于遍历一组值或基于范围的循环处理。

遍历

用于遍历时的调用形式如下。

foreach(loop_var arg1 arg2 ...)
  # ...
endforeach()

foreach第一个参数为每次循环中用于表示当前遍历元素的变量名,其后的参数为要进行遍历的元素。例如以下命令执行后会依次输出1到5。

foreach(item 1 2 3 4 5)
    message(${item})
endforeach()

结合变量引用,foreach可以遍历列表变量的元素,例如:

set(A 1 2 3 4 5)
foreach(item ${A})
    message(${item})
endforeach()
范围循环

范围循环有两种调用形式。

\1. foreach(loop_var RANGE total)

表示从0循环到total,例如下面命令会依次输出1到10。

foreach(item RANGE 10)
    message(${item})
endforeach()

\2. foreach(loop_var RANGE start stop [step])

表示从start循环到stop,步进为step,步进参数可选,默认为1。例如下面命令输出1到10以内的奇数。

foreach(item RANGE 1 10 2)
    message(${item})
endforeach()

while命令

while/endwhile命令用于条件控制的循环,调用形式如下:

while(condition)
  # ...
endwhile()

其中while命令中的条件判断和if命令支持的相同。

break/continue命令

break()和continue()命令与C语言中的类似,用于跳出循环和进行下一次循环,此处不再赘述。

数学运算

因为cmake的变量值都是字符串,所以并不能像其他语言那样直接对变量进行运算操作,而是需要用到math命令。math命令的调用形式如下。

math(EXPR <output-variable> <math-expression>)

其中第一个参数EXPR关键字是固定的,第二个参数接收一个变量名,第三个参数是要运算的表达式,math命令计算表达式后,将结果设置到第二个参数对应的变量中。例如

math(EXPR NUM "5*2")
# NUM值为10

目前,math命令的功能还是比较简单,支持的运算符有+, -, *, /, %, |, &, ^, ~, <<, >>, (, ),它们的含义和C语言中的运算符相同。math命令不支持大于小于等比较,不支持布尔表达式的运算,也不支持浮点类型的运算,只能够进行整形数值的运算。math命令对表达式的检查也比较有限,某些情况下可能并不会报错。

另外,建议表达式都使用引号参数形式传递,因为当表达式中含有空格和括号时,如果使用普通参数传递需要进行转义处理,较为麻烦。

表达式中也可以包含变量引用,例如math(EXPR NUM "${NUM} + 1"),实现对变量NUM的值加1。

Cmake 语法

if(condition)
这个是用来判断变量是否定义过的

# CMakeLists.txt
# CMake最低版本要求
cmake_minimum_required(VERSION 3.5)

# 项目名称
project(test_6)

if(WIN32)
	message(STATUS "Now is windows")
elseif(APPLE)
	message(STATUS "Now is Apple systens.")
elseif(UNIX)
	message(STATUS "Now is UNIX-like OS's.")
endif()

逻辑运算

# 取反运算
if(NOT <condition>)

# 与运算
if(<cond1> AND <cond2>)

# 或运算
if(<cond1> OR <cond2>)

特殊语句

if(COMMAND command-name)
如果给定名称是可以调用的命令,宏或函数,则为true。

if(POLICY policy-id)
如果给定名称是现有策略(格式为CMP<NNNN>),则为True 。

if(TARGET target-name)
如果给定名称是通过调用调用创建的现有逻辑目标名称,则为True。 add_executable(), add_library(), 要么 add_custom_target() 已经被调用的命令(在任何目录中)。

if(TEST test-name)
如果给定名称是由测试人员创建的现有测试名称,则为True add_test() 命令。

if(EXISTS path-to-file-or-directory)
如果指定的文件或目录存在,则为True。仅针对完整路径定义行为。解析符号链接,即,如果命名的文件或目录是符号链接,则如果符号链接的目标存在,则返回true。

if(file1 IS_NEWER_THAN file2)
如果file1是更新版本file2或两个文件之一不存在,则为true 。仅针对完整路径定义行为。如果文件时间戳完全相同,则IS_NEWER_THAN比较将返回true,这样,在出现平局的情况下将发生任何相关的生成操作。这包括为file1和file2传递相同文件名的情况。

if(IS_DIRECTORY path-to-directory)
如果给定名称是目录,则为True。仅针对完整路径定义行为。

if(IS_SYMLINK file-name)
如果给定名称是符号链接,则为True。仅针对完整路径定义行为。

if(IS_ABSOLUTE path)
如果给定路径是绝对路径,则为True。

if(<variable|string> MATCHES regex)
如果给定的字符串或变量的值与给定的常规条件匹配,则为true。有关正则表达式格式,请参见正则表达式规范。 ()组被捕获在CMAKE_MATCH_<n> 变量。

if(<variable|string> LESS <variable|string>)
如果给定的字符串或变量的值是有效数字且小于右边的数字,则为true。

if(<variable|string> GREATER <variable|string>)
如果给定的字符串或变量的值是有效数字并且大于右侧的数字,则为true。

if(<variable|string> EQUAL <variable|string>)
如果给定的字符串或变量的值是有效数字并且等于右侧的数字,则为true。

if(<variable|string> LESS_EQUAL <variable|string>)
如果给定的字符串或变量的值是有效数字且小于或等于右侧的数字,则为true。

if(<variable|string> GREATER_EQUAL <variable|string>)
如果给定的字符串或变量的值是有效数字并且大于或等于右侧的数字,则为true。

if(<variable|string> STRLESS <variable|string>)
如果给定的字符串或变量的值在字典上小于右侧的字符串或变量,则为true。

if(<variable|string> STRGREATER <variable|string>)
如果给定的字符串或变量的值在字典上大于右侧的字符串或变量,则为true。

if(<variable|string> STREQUAL <variable|string>)
如果给定的字符串或变量的值在字典上等于右侧的字符串或变量,则为true。

if(<variable|string> STRLESS_EQUAL <variable|string>)
如果给定的字符串或变量的值在字典上小于或等于右侧的字符串或变量,则为true。

if(<variable|string> STRGREATER_EQUAL <variable|string>)
如果给定的字符串或变量的值在字典上大于或等于右侧的字符串或变量,则为true。

if(<variable|string> VERSION_LESS <variable|string>)
按组件的整数版本号比较(版本格式为 major[.minor[.patch[.tweak]]],省略的组件视为零)。任何非整数版本组件或版本组件的非整数结尾部分均会在该点处有效截断字符串。

if(<variable|string> VERSION_GREATER <variable|string>)
按组件的整数版本号比较(版本格式为 major[.minor[.patch[.tweak]]],省略的组件视为零)。任何非整数版本组件或版本组件的非整数结尾部分均会在该点处有效截断字符串。

if(<variable|string> VERSION_EQUAL <variable|string>)
按组件的整数版本号比较(版本格式为 major[.minor[.patch[.tweak]]],省略的组件视为零)。任何非整数版本组件或版本组件的非整数结尾部分均会在该点处有效截断字符串。

if(<variable|string> VERSION_LESS_EQUAL <variable|string>)
按组件的整数版本号比较(版本格式为 major[.minor[.patch[.tweak]]],省略的组件视为零)。任何非整数版本组件或版本组件的非整数结尾部分均会在该点处有效截断字符串。

if(<variable|string> VERSION_GREATER_EQUAL <variable|string>)
按组件的整数版本号比较(版本格式为 major[.minor[.patch[.tweak]]],省略的组件视为零)。任何非整数版本组件或版本组件的非整数结尾部分均会在该点处有效截断字符串。

if(<variable|string> IN_LIST <variable>)
如果给定元素包含在命名列表变量中,则为true。

if(DEFINED <name>|CACHE{<name>}|ENV{<name>})
如果<name>定义了具有给定的变量,缓存变量或环境变量,则为true 。变量的值无关紧要。请注意,宏参数不是变量。

if((condition) AND (condition OR (condition)))
首先评估括号内的条件,然后像前面的示例一样评估其余条件。如果有嵌套的括号,则将最里面的括号作为包含它们的条件的一部分进行评估。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐