江西省网站建设_网站建设公司_GitHub_seo优化
2025/12/28 22:50:54 网站建设 项目流程

 31.4 CMakeLists.txt 语法规则


在上一小节中,笔者通过几个简单地示例向大家演示了 cmake 的使用方法,由此可知,cmake 的使用方法其实还是非常简单的,重点在于编写 CMakeLists.txt,CMakeLists.txt 的语法规则也简单,并没有 Makefile的语法规则那么复杂难以理解!

本小节我们来学习 CMakeLists.txt 的语法规则。
简单地语法介绍


 注释
在 CMakeLists.txt 文件中,使用“#”号进行单行注释,譬如:

#
# 这是注释信息
#
cmake_minimum_required(VERSION 3.16)
project(HELLO)

 大多数脚本语言都是使用#”号进行注释。

 

 命令(command )
通常在 CMakeLists.txt 文件中,使用最多的是命令,譬如上例中的 cmake_minimum_required、project 都是命令;

命令的使用方式有点类似于 C 语言中的函数,因为命令后面需要提供一对括号,并且通常需要我们提供参数,多个参数使用 空格分隔而不是逗号“,”,这是与函数不同的地方。命令的语法格式如下所示:

command(参数 1 参数 2 参数 3 ...)

 

不同的命令所需的参数不同,需要注意的是,参数可以分为必要参数和可选参数(通常称为选项),很多命令都提供了这两类参数,

  • 必要参数使用<参数>表示,
  • 可选参数使用[参数]表示,譬如 set 命令:
set(<variable> <value>... [PARENT_SCOPE])

set 命令用于设置变量,第一个参数<variable>和第二个参数<value>是必要参数,在参数列表(…表示参数个数没有限制)的  最后可以添加一个  可选参数 PARENT_SCOPE(PARENT_SCOPE 选项), 既然是可选的,那就不是必须的,根据实际使用情况确定是否需要添加。

在 CMakeLists.txt 中,命令名不区分大小写,可以使用大写字母或小写字母书写命令名,譬如:

project(HELLO) #小写
PROJECT(HELLO) #大写

 这俩的效果是相同的,指定的是同一个命令,并没区别;

这个主要看个人喜好,个人喜欢用小写字母,主要是为了和变量区分开来,因为 cmake 的内置变量其名称都是使用大写字母组成的。

 

 变量(variable )
在 CMakeLists.txt 文件中可以使用变量,使用 set 命令可以对变量进行设置,譬如:

# 设置变量 MY_VAL
set(MY_VAL "Hello World!")

 上例中,通过 set 命令对变量 MY_VAL 进行设置,将其内容设置为"Hello World!";那如何引用这个变量呢?

这与 Makefile 是相同的,通过${MY_VAL}方式来引用变量,如下所示:

#设置变量 MY_VAL
set(MY_VAL "Hello World!")
#引用变量 MY_VAL
message(${MY_VAL})

 变量可以分为 cmake 内置变量以及自定义变量,譬如上例中所定义的 MY_VAL 就是一个自定义变量;

譬如在 31.3.5 小节中所使用的  LIBRARY_OUTPUT_PATH  和  EXECUTABLE_OUTPUT_PATH  变量则是cmake 的内置变量,每一个内置变量都有自己的含义,像这样的内置变量还有很多,稍后向大家介绍。


 

部分常用命令


cmake 提 供 了 很 多 命 令 , 每 一 个 命 令 都 有 它 自 己 的 功 能 、 作 用 , 通 过 这 个 链 接 地 址

https://cmake.org/cmake/help/v3.5/manual/cmake-commands.7.html

可以查询到所有的命令及其相应的介绍、使用方法等等,如下所示:

image

 图 31.4-1 cmake 命令查询

 

大家可以把这个链接地址保存起来,可以把它当成字典的形式在有需要的时候进行查询,由于命令非常多,笔者不可能将所有命令都给大家介绍一遍,这里给大家介绍一些基本的命令,如下表所示:

command 说明
add_executable 可执行程序目标
add_library 库文件目标
add_subdirectory 去指定目录中寻找新的 CMakeLists.txt 文件
aux_source_directory 收集目录中的文件名并赋值给变量
cmake_minimum_required 设置 cmake 的最低版本号要求
get_target_property 获取目标的属性
include_directories 设置所有目标头文件的搜索路径,相当于 gcc 的-I 选项link_directories 设置所有目标库文件的搜索路径,相当于 gcc 的-L 选项
link_libraries 设置所有目标需要链接的库
list 列表相关的操作
message 用于打印、输出信息
project 设置工程名字
set 设置变量
set_target_properties 设置目标属性
target_include_directories 设置指定目标头文件的搜索路径
target_link_libraries 设置指定目标库文件的搜索路径
target_sources 设置指定目标所需的源文件

 表 31.4.1 常用基本的命令


接下来详细地给大家介绍每一个命令。


 add_executable
add_executable 命令:用于添加一个可执行程序目标,并设置目标所需的源文件,该命令定义如下所示:
add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
该命令提供了一些可选参数,这些可选参数的含义笔者就不多说了,通常不需要加入,具体的含义大家
可以自己查看 cmake 官方文档(https://cmake.org/cmake/help/v3.5/command/add_executable.html);只需传入目标名和对应的源文件即可,譬如:

#生成可执行文件 hello
add_executable(hello 1.c 2.c 3.c)

 定义了一个可执行程序目标 hello,生成该目标文件所需的源文件为 1.c、2.c 和 3.c。需要注意的是,源文件路径既可以使用相对路径、也可以使用绝对路径,相对路径被解释为相对于当前源码路径

(注意,这里源码指的是 CMakeLists.txt 文件,因为 CMakeLists.txt 被称为 cmake 的源码,若无特别说明,后续将沿用这个概念!)。


 add_library
add_library 命令用于添加一个库文件目标,并设置目标所需的源文件,该命令定义如下所示:

add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...])

 第一个参数 name 指定目标的名字,参数 source1…source2 对应源文件列表;add_library 命令默认生成的库文件是静态库文件,通过 SHARED 选项可使其生成动态库文件,具体的使用方法如下:

#生成静态库文件 libmylib.a

add_library(mylib STATIC 1.c 2.c 3.c)
#生成动态库文件 libmylib.so
add_library(mylib SHARED 1.c 2.c 3.c)

 与 add_executable 命令相同,add_library 命令中源文件既可以使用相对路径指定、也可以使用绝对路径指定,相对路径被解释为相对于当前源码路径。

不管是 add_executable、还是 add_library,它们所定义的目标名在整个工程中必须是唯一的,不可出现两个目标名相同的目标。


 add_subdirectory

add_subdirectory 命令告诉 cmake 去指定的目录中寻找源码并执行它,有点像 Makefile 的 include,其定义如下所示:
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

 参数source_dir指定一个目录,告诉cmake去该目录下寻找CMakeLists.txt文件并执行它;

参数binary_dir指定了一个路径,该路径作为子源码(调用 add_subdirectory 命令的源码称为当前源码或父源码,被执行的源码称为子源码)的输出文件(cmake 命令所产生的中间文件)目录,

binary_dir 参数是一个可选参数,如果没有显式指定,则会使用一个默认的输出文件目录;

为了后续便于表述,我们将输出文件目录称为BINARY_DIR

譬如工程目录结构如下所示:

├── build
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.c

顶层 CMakeLists.txt 文件内容如下所示:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project("HELLO")
# 告诉 cmake 去 src 目录下寻找 CMakeLists.txt
add_subdirectory(src)

 src 目录下的 CMakeLists.txt 文件:

# src 下的 CMakeLists.txt
add_executable(hello main.c)

 进入到 build 目录下,执行 cmake、make 进行构建编译;在本例中,顶层源码对应的输出文件会存放在build 目录,也就是执行 cmake 命令所在目录;子源码(src 目录下的 CMakeLists.txt)对应的输出文件会存放在 build/src 目录,包括生成的可执行文件默认会与这些中间文件放置在同一个目录,如下所示:

├── build
│ ├── CMakeCache.txt
│ ├── CMakeFiles
│ ├── cmake_install.cmake
│ ├── Makefile
│ └── src
│ ├── CMakeFiles
│ ├── cmake_install.cmake
│ ├── hello
│ └── Makefile
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.c

 



所以由此可知,当前源码调用add_subdirectory命令执行子源码时,若没有为子源码指定BINARY_DIR,
默认情况下,会在当前源码的 BINARY_DIR 中创建与子目录(子源码所在目录)同名的文件夹,将其作为子源码的 BINARY_DIR。
接下来我们修改顶层 CMakeLists.txt 文件:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project("HELLO")
# 告诉 cmake 去 src 目录下寻找 CMakeLists.txt
add_subdirectory(src output)

 指定子源码的 BINARY_DIR 为 output,这里使用的是相对路径方式,add_subdirectory 命令对于相对路径的解释为:相对于当前源码的 BINARY_DIR;

修改完成之后,再次进入到 build 目录下执行 cmake、make命令进行构建、编译,此时会在 build 目录下生成一个 output 目录,这就是子源码的 BINARY_DIR。

设置 BINARY_DIR 可以使用相对路径、也可以是绝对路径,相对路径则是相对于当前源码的BINARY_DIR,并不是当前源码路径,这个要理解。
通过 add_subdirectory 命令加载、执行一个外部文件夹中的源码,既可以是当前源码路径的子目录、也可以是与当前源码路径平级的目录亦或者是当前源码路径上级目录等等;对于当前源码路径的子目录,不强制调用者显式指定子源码的 BINARY_DIR;

如果不是当前源码路径的子目录,则需要调用者显式指定
BINARY_DIR,否则执行源码时会报错。接下来进行测试,譬如工程目录结构如下所示:

├── build
├── CMakeLists.txt
├── lib
│ └── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.c

 这里一共有 3 个 CMakeLists.txt 文件,lib 目录和 src 目录是平级关系,顶层 CMakeLists.txt 内容如下:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project("HELLO")
# 加载 src 目录下的源码
add_subdirectory(src)
src 目录下的 CMakeLists.txt:
# src 目录 CMakeLists.txt
add_executable(hello main.c)
# 加载平级目录 lib 中的源码
add_subdirectory(../lib)

 lib 目录下的 CMakeLists.txt 内容为空。此时调用 add_subdirectory 加载 lib 目录的源码时并为显式指定BINARY_DIR,我们看看会怎么样,进入到 build 目录下,执行 cmake 命令,如下所示:

 

image

图 31.4-2 cmake 打印信息

 

果不其然确实发生了报错,而且提示我们 add_subdirectory 命令必须要指定 BINARY_DIR,那我们将 src目录下的 CMakeLists.txt 进行修改,显式指定 BINARY_DIR,如下所示:

# src 目录 CMakeLists.txt
add_executable(hello main.c)
# 加载平级目录 lib 中的源码
add_subdirectory(../lib output)

 接着再次执行 cmake(每次执行 cmake 前进行清理,将 build 目录下生成的所有文件全部删除):

image

 图 31.4-3 cmake 打印信息

 

可以看到这次执行 cmake 没有报错打印了。


 aux_source_directory
aux_source_directory 命令会查找目录中的所有源文件,其命令定义如下:

aux_source_directory(<dir> <variable>)

 从指定的目录中查找所有源文件,并将扫描到的源文件路径信息存放到<variable>变量中,譬如目录结构如下:

├── build
├── CMakeLists.txt
└── src
├── 1.c
├── 2.c
├── 2.cpp
└── main.c

 CMakeLists.txt 内容如下所示:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project("HELLO")
# 查找 src 目录下的所有源文件
aux_source_directory(src SRC_LIST)
message("${SRC_LIST}") # 打印 SRC_LIST 变量

 进入到 build 目录下,执行 cmake ..命令,打印信息如下所示:

image

 图 31.4-4 cmake 命令打印信息

 

由此可见,aux_source_directory 会将扫描到的每一个源文件添加到 SRC_LIST 变量中,组成一个字符串列表,使用分号“;”分隔。
同理,aux_source_directory 既可以使用相对路径,也可以使用绝对路径,相对路径是相对于当前源码路径。


 get_target_property 和 和 set_target_properties
分别用于获取/设置目标的属性,这个后面再给大家进行专题介绍。


 include_directories
include_directories 命令用于设置头文件的搜索路径,相当于 gcc 的-I 选项,其定义如下所示:

include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])

 默认情况下会将指定目录添加到表 头文件搜索列表(可以认为每一个 CMakeLists.txt 源码都有自己的头文件搜索列表) )的最后面,可以通过设置 CMAKE_INCLUDE_DIRECTORIES_BEFORE 变量为 ON 来改变它默认行为,将目录添加到列表前面。也可以在每次调用 include_directories 命令时使用 AFTER 或 BEFORE

选项来指定是添加到列表的前面或者后面。如果使用 SYSTEM 选项,会把指定目录当成系统的搜索目录。

既可以使用绝对路径来指定头文件搜索目录、也可以使用相对路径来指定,相对路径被解释为当前源码路径的相对路径。
譬如工程目录结构如下所示:

├── build
├── CMakeLists.txt
├── include
│ └── hello.h
└── main.c

源文件 main.c 中使用了 include 目录下的头文件 hello.h,CMakeLists.txt 内容如下:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project("HELLO")
include_directories(include)
add_executable(hello main.c)

 使用 include_directories 命令将当前目录下的 include 文件夹添加到头文件搜索列表中,进入 build 目录下,执行 cmake、make 进行构建、编译,编译过程是没有问题的,不会报错提示头文件找不到;但如果去掉 include_directories(include)这条命令,编译肯定会报错,大家可以动手试试!

默认情况下,include 目录被添加到头文件搜索列表的最后面,通过 AFTER 或 BEFORE 选项可显式指定添加到列表后面或前面:

# 添加到列表后面
include_directories(AFTER include)
# 添加到列表前面
include_directories(BEFORE include)

 当调用 add_subdirectory 命令加载子源码时,会将 include_directories 命令包含的目录列表向下传递给子源码(子源码从父源码中继承过来),我们测试下,譬如工程目录结构如下所示:

├── build
├── CMakeLists.txt
├── include
│ └── hello.h
└── src
├── CMakeLists.txt
└── main.c

 src 目录下 main.c 源文件中使用了 hello.h 头文件,顶层 CMakeLists.txt 内容如下所示:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project("HELLO")
include_directories(include)
add_subdirectory(src)

 顶层 CMakeLists.txt 源码中调用了 include_directories 将 include 目录添加到当前源码的头文件搜索列表中,接着调用 add_subdirectory 命令加载、执行子源码;src 目录下 CMakeLists.txt 内容如下所示:

# src 目录 CMakeLists.txt
add_executable(hello main.c)

 进入到 build 目录,进行构建、编译,整个编译过程是没有问题的。

 link_directories 和 和 link_libraries
link_directories 命令用于设置库文件的搜索路径,相当于 gcc 编译器的-L 选项;link_libraries 命令用于设置需要链接的库文件,相当于 gcc 编译器的-l 选项;命令定义如下所示:

link_directories(directory1 directory2 ...)
link_libraries([item1 [item2 [...]]]
[[debug|optimized|general] <item>] ...)

 link_directories 会将指定目录添加到 库文件搜索列表(可以认为每一个 CMakeLists.txt 源码都有自己的库文件搜索列表) )中;同理,link_libraries 命令会将指定库文件添加到链接库列表。link_directories 命令可以使用绝对路径或相对路径指定目录,相对路径被解释为当前源码路径的相对路径。

譬如工程目录结构如下所示:

├── build
├── CMakeLists.txt
├── include
│ └── hello.h
├── lib
│ └── libhello.so
└── main.c

 在lib目录下有一个动态库文件libhello.so,编译链接main.c源文件时需要链接libhello.so;CMakeLists.txt

文件内容如下所示:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project("HELLO")
include_directories(include)
link_directories(lib)
link_libraries(hello)
add_executable(main main.c)

 库文件名既可以使用简写,也可以库文件名的全称,譬如:

# 简写
link_libraries(hello)
# 全称
link_libraries(libhello.so)

 link_libraries 命令也可以指定库文件的全路径(绝对路径 /开头),如果不是/开头,link_libraries 会认为调用者传入的是库文件名,而非库文件全路径,譬如上述 CMakeLists.txt 可以修改为下面这种方式:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project("HELLO")
include_directories(include)link_libraries(${PROJECT_SOURCE_DIR}/lib/libhello.so)
add_executable(main main.c)

 与 include_directories 命令相同,当调用 add_subdirectory 命令加载子源码时,会将 link_directories 命令包含的目录列表以及 link_libraries 命令包含的链接库列表向下传递给子源码(子源码从父源码中继承过来)。

这里不再演示了,大家可以自己测试下。


 list
list 命令是一个关于列表操作的命令,譬如获取列表的长度、从列表中返回由索引值指定的元素、将元素追加到列表中等等。命令定义如下:

list(LENGTH <list> <output variable>)
list(GET <list> <element index> [<element index> ...]
<output variable>)
list(APPEND <list> [<element> ...])
list(FIND <list> <value> <output variable>)
list(INSERT <list> <element_index> <element> [<element> ...])
list(REMOVE_ITEM <list> <value> [<value> ...])
list(REMOVE_AT <list> <index> [<index> ...])
list(REMOVE_DUPLICATES <list>)
list(REVERSE <list>)
list(SORT <list>)

 列表这个概念还没给大家介绍,列表其实就是字符串数组(或者叫字符串列表、字符串数组),稍后再向大家说明。

LENGTH 选项用于返回列表长度;
GET 选项从列表中返回由索引值指定的元素;
APPEND 选项将元素追加到列表后面;
FIND 选项将返回列表中指定元素的索引值,如果未找到,则返回-1。
INSERT 选项将向列表中的指定位置插入元素。
REMOVE_AT 和 REMOVE_ITEM 选项将从列表中删除元素,不同之处在于 REMOVE_ITEM 将删除给定的元素,而 REMOVE_AT 将删除给定索引值的元素。
REMOVE_DUPLICATES 选项将删除列表中的重复元素。
REVERSE 选项就地反转列表的内容。
SORT 选项按字母顺序对列表进行排序。


 message
message 命令用于打印、输出信息,类似于 Linux 的 echo 命令,命令定义如下所示:

message([<mode>] "message to display" ...)

 可选的 mode 关键字用于确定消息的类型,如下:

mode 说明
none(无) 重要信息、普通信息
STATUS 附带信息
WARNING CMake 警告,继续处理
AUTHOR_WARNING CMake 警告(开发),继续处理
SEND_ERROR CMake 错误,继续处理,但跳过生成
FATAL_ERROR CMake 错误,停止处理和生成DEPRECATION 如 果 变 量 CMAKE_ERROR_DEPRECATED 或
CMAKE_WARN_DEPRECATED 分别启用,则 CMake 弃用错
误或警告,否则没有消息。

 表 31.4.2 message 命令 mode 关键字说明

 

所以可以使用这个命令作为 CMakeLists.txt 源码中的输出打印语句,譬如:

# 打印"Hello World"
message("Hello World!")

  project

project 命令用于设置工程名称:

# 设置工程名称为 HELLO
project(HELLO)

 执行这个之后会引入两个变量:HELLO_SOURCE_DIR 和 HELLO_BINARY_DIR,注意这两个变量名的前缀就是工程名称,HELLO_SOURCE_DIR 变量指的是 HELLO 工程源码目录、HELLO_BINARY_DIR 变量指的是 HELLO 工程源码的输出文件目录;我们可以使用 message 命令打印变量,譬如 CMakeLists.txt 内

容如下所示:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project("HELLO")
message(${HELLO_SOURCE_DIR})
message(${HELLO_BINARY_DIR})

 进入 build 目录下,执行 cmake:

image

 图 31.4-5 cmake 命令打印信息

 

但如果不加入 project(HELLO)命令,这两个变量是不存在的;工程源码目录指的是顶层源码所在目录,
cmake 定义了两个等价的变量 PROJECT_SOURCE_DIR 和 PROJECT_BINARY_DIR,通常在 CMakeLists.txt源码中都会使用这两个等价的变量。
通常只需要在顶层 CMakeLists.txt 源码中调用 project 即可!


 set
set 命令用于设置变量,命令定义如下所示:

set(<variable> <value>... [PARENT_SCOPE])

 设置变量的值,可选参数 PARENT_SCOPE 影响变量的作用域,这个我们稍后再说。

譬如 CMakeLists.txt 源码内容如下所示:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project("HELLO")
# set 命令
set(VAR1 Hello) #设置变量 VAR1=Hello
set(VAR2 World) #设置变量 VAR2=World
# 打印变量
message(${VAR1} " " ${VAR2})

对应的打印信息:

image

 图 31.4-6 cmake 打印信息

 

字符串列表
通过 set 命令实现字符串列表,如下所示:

# 字符串列表
set(SRC_LIST 1.c 2.c 3.c 4.c 5.c)

 此时 SRC_LIST 就是一个列表,它包含了 5 个元素(1.c、2.c、3.c、4.c、5.c),列表的各个元素使用分号“;”分隔,如下:

SRC_LIST = 1.c;2.c;3.c;4.c;5.c #列表
我们来测试一下,譬如 CMakeLists.txt 源码内容如下所示:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.5")
project("HELLO")
# set 命令
set(SRC_LIST 1.c 2.c 3.c 4.c 5.c)
# 打印变量message(${SRC_LIST})

 执行 cmake 命令打印信息如下:

image

 图 31.4-7 cmake 打印信息

 

乍一看这个打印信息你是不是觉得 SRC_LIST 就是一个普通的变量(SRC_LIST=1.c2.c3.c4.c5.c),并不是列表呢?事实并非如此,我们可以修改 message 命令,将${SRC_LIST}放置在双引号中,如下:

# 打印变量
message("${SRC_LIST}")

 再次执行 cmake,打印信息如下:

image

 图 31.4-8 cmake 打印信息

 

可以看到此时打印出来的确实是一个列表,为何加了双引号就会这样呢?关于双引号的作用请看 31.4.4小节。既然是列表,那自然可以使用 list 命令对列表进行相关的操作:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.5")
project("HELLO")
# 列表
set(SRC_LIST main.c world.c hello.c)
message("SRC_LIST: ${SRC_LIST}")
#列表操作
list(LENGTH SRC_LIST L_LEN)
message("列表长度: ${L_LEN}")
list(GET SRC_LIST 1 VAR1)
message("获取列表中 index=1 的元素: ${VAR1}")
list(APPEND SRC_LIST hello_world.c) #追加元素
message("SRC_LIST: ${SRC_LIST}")
list(SORT SRC_LIST) #排序
message("SRC_LIST: ${SRC_LIST}")

 cmake 打印信息如下:

image

 图 31.4-9 cmake 打印信息

 

除此之外,在 cmake 中可以使用循环语句依次读取列表中的各个元素,后续再向大家介绍。


 target_include_directories 和 和 target_link_libraries
target_include_directories 命令为指定目标设置头文件搜索路径,而 target_link_libraries 命令为指定目标设置链接库文件,这听起来跟 include_directories 和 link_libraries 命令有着相同的作用,确实如此,它们的功能的确相同,但是在一些细节方面却有不同,关于它们之间的区别稍后再给大家进行解释!
target_include_directories 和 target_link_libraries 命令定义如下所示:

target_include_directories(<target> [SYSTEM] [BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
target_link_libraries(<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)

 这俩命令都有一个相同的参数<target>目标,这个目标指的就是譬如 add_executable、add_library 命令所创建的目标。

首先对于 target_include_directories 命令来说,SYSTEM、BEFORE 这两个选项与include_directories 命令中 SYSTEM、BEFORE 选项的意义相同,这里不再多说!

我们重点关注的是 INTERFACE|PUBLIC|PRIVATE 这三个选项有何不同?通过一个示例向大家说明,
譬如工程目录结构如下所示:

├── build //build 目录
├── CMakeLists.txt
├── hello_world //生成 libhello_world.so,调用 libhello.so 和 libworld.so
│ ├── CMakeLists.txt
│ ├── hello //生成 libhello.so
│ │ ├── CMakeLists.txt
│ │ ├── hello.c
│ │ └── hello.h //libhello.so 对外头文件
│ ├── hello_world.c
│ ├── hello_world.h //libhello_world.so 对外头文件
│ └── world //生成 libworld.so
│ ├── CMakeLists.txt
│ ├── world.c
│ └── world.h //libworld.so 对外头文件
└── main.c

调用关系:

├────libhello.so

 可执行文件────libhello_world.so

├────libworld.so

 根据以上工程,我们对 INTERFACE、PUBLIC、PRIVATE 三个关键字进行说明:

PRIVATE :私有的。main.c 程序调用了 libhello_world.so,生成 libhello_world.so 时,只在 hello_world.c
中包含了 hello.h,libhello_world.so 对外的头文件——hello_world.h 中不包含 hello.h。而且 main.c 不会调用hello.c 中的函数,或者说 main.c 不知道 hello.c 的存在,它只知道 libhello_world.so 的存在;那么在hello_world/CMakeLists.txt 中应该写入:

target_link_libraries(hello_world PRIVATE hello)
target_include_directories(hello_world PRIVATE hello)

 INTERFACE :接口。生成 libhello_world.so 时,只在 libhello_world.so 对外的头文件——hello_world.h中包含了 hello.h,hello_world.c 中不包含 hello.h,即 libhello_world.so 不使用 libhello.so 提供的功能,但是main.c 需要使用 libhello.so 中的功能。那么在 hello_world/CMakeLists.txt 中应该写入:

target_link_libraries(hello-world INTERFACE hello)
target_include_directories(hello-world INTERFACE hello)

 PUBLIC :公开的。PUBLIC = PRIVATE + INTERFACE。生成 libhello_world.so 时,在 hello_world.c 和hello_world.h 中都 包含了 hello.h。 并且 main.c 中 也需要使 用 libhello.so 提供的 功能。 那么 在hello_world/CMakeLists.txt 中应该写入:

target_link_libraries(hello-world PUBLIC hello)
target_include_directories(hello-world PUBLIC hello)

 不知道大家看懂了没有,其实理解起来很简单,对于 target_include_directories 来说,这些关键字用于指示 何时需要传递给目标的 包含目录 列表,指定了 包含目录列表的使用范围(scope):

 当使用 PRIVATE 关键字修饰时,意味着 包含目录列表仅用于当前目标;
 当使用 INTERFACE 关键字修饰时,意味着 包含目录列表不用于当前目标、只能用于依赖该目标的其它目标,也就是说 cmake 会将 包含目录列表传递给当前目标的依赖目标;
 当使用 PUBLIC 关键字修饰时,这就是以上两个的集合, 包含目录列表既用于当前目标、也会传递给当前目标的依赖目标。
对于 target_link_libraries 亦是如此,只不过 包含目录列表换成了 链接库列表。譬如:
target_link_libraries(hello_world INTERFACE hello):表示目标 hello_world 不需要链接 hello 库,但是对于 hello_world 目标的依赖目标(依赖于 hello_world 的目标)它们需要链接 hello 库。
以上便是笔者对 INTERFACE、PUBLIC、PRIVATE 这三个关键字的概括性理解,所以整出这几个关键字主要还是为了控制 包含目录列表或表 链接库列表的使用范围,这就是 target_include_directories、target_link_libraries 命令与 include_directories、link_libraries 命令的不同之处。target_include_directories()、target_link_libraries()的功能完全可以使用 include_directories()、link_libraries()来实现。但是笔者强烈建议大家使用 target_include_directories()和 target_link_libraries()。为什么?保持清晰!
include_directories()、link_libraries()是针对当前源码中的所有目标,并且还会向下传递(譬如通过add_subdirectory 加载子源码时,也会将其传递给子源码)。在一个大的工程当中,这通常不规范、有时还会编译出现错误、混乱,所以我们应尽量使用 target_include_directories()和 target_link_libraries(),保持整个
工程的目录清晰。



总结
本小节内容到此结束了,给大家介绍了一些基本、常用的命令,并进行了详细的解释说明,除此之外,还有很多的命令并未提及,我们会在后面进行专题的介绍,大家要自己多动手、多多练习,这样才能越来越熟练!

 

 

 

 

 


31.4.3 部分常用变量


变量也是 cmake 中的一个重头戏,cmake 提供了很多内置变量,每一个变量都有它自己的含义,通过这个链接地址 https://cmake.org/cmake/help/v3.5/manual/cmake-variables.7.html 可以查询到所有的内置变量及其相应的介绍,如下所示:

image

 图 31.4-10 cmake 内置变量

在这一份文档中,对变量进行分类,分为:提供信息的变量、改变行为的变量、描述系统的变量、控制编译的变量等等,笔者也按照这个分类给大家介绍一些基本、常用的变量。
 提供信息的变量
顾名思义,这种变量可以提供某种信息,既然如此,那么我们通常只需要读取变量即可,而不需要对变量进行修改:

变量 说明
PROJECT_SOURCE_DIR 工程顶层目录,也就是顶层 CMakeLists.txt 源码所在目录
PROJECT_BINARY_DIR 工 程 BINARY_DIR , 也 就 是 顶 层 CMakeLists.txt 源 码 的
BINARY_DIR
CMAKE_SOURCE_DIR 与 PROJECT_SOURCE_DIR 等价
CMAKE_BINARY_DIR 与 PROJECT_BINARY_DIR 等价
CMAKE_CURRENT_SOURCE_DIR 当前源码所在路径
CMAKE_CURRENT_BINARY_DIR 当前源码的 BINARY_DIR
CMAKE_MAJOR_VERSION cmake 的主版本号CMAKE_MINOR_VERSION cmake 的次版本号
CMAKE_VERSION cmake 的版本号(主+次+修订)
PROJECT_VERSION_MAJOR 工程的主版本号
PROJECT_VERSION_MINOR 工程的次版本号
PROJECT_VERSION 工程的版本号
CMAKE_PROJECT_NAME 工程的名字
PROJECT_NAME 工程名,与 CMAKE_PROJECT_NAME 等价

 


图 31.4-11 提供信息的变量

PROJECT_SOURCE_DIR 和 PROJECT_BINARY_DIR

 


PROJECT_SOURCE_DIR 变量表示工程的顶级目录,也就是顶层 CMakeLists.txt 文件所在目录;
PROJECT_BINARY_DIR 变量表示工程的 BINARY_DIR,也就是顶层 CMakeLists.txt 源码对应的BINARY_DIR(输出文件目录)。
譬如工程目录结构如下所示:

├── build
├── CMakeLists.txt
└── main.c

 


CMakeLists.txt 文件内容如下:

# CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project(HELLO)
message(${PROJECT_SOURCE_DIR})
message(${PROJECT_BINARY_DIR})

 


CMakeLists.txt 中我们打印了 PROJECT_SOURCE_DIR 和 PROJECT_BINARY_DIR 变量,进入到 build目录下,执行 cmake:

image

 

图 31.4-12 cmake 打印信息
从打印信息可知,PROJECT_SOURCE_DIR 指的就是工程的顶层 CMakeLists.txt 源码所在路径,而PROJECT_BINARY_DIR 指的是我们执行 cmake 命令的所在目录,也是顶层 CMakeLists.txt 源码的BINARY_DIR。

 CMAKE_SOURCE_DIR 和 和 CMAKE_BINARY_DIR与上面两个等价,大家自己打印出来看看便知!
 CMAKE_CURRENT_SOURCE_DIR 和 和 CMAKE_CURRENT_BINARY_DIR指的是当前源码的路径以及当前源码的 BINARY_DIR,通过示例来看看,譬如工程目录结构如下所示:

├── build
├── CMakeLists.txt
├── main.c
└── src
└── CMakeLists.txt

 


顶层 CMakeLists.txt 文件通过 add_subdirectory 加载子目录 src 下的 CMakeLists.txt,顶层 CMakeLists.txt文件内容如下:

#顶层的 CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project(HELLO)
add_subdirectory(src)

 


src 目录下 CMakeLists.txt 文件内容如下所示:

# src 下的 CMakeLists.txt
message(${PROJECT_SOURCE_DIR})
message(${PROJECT_BINARY_DIR})
message(${CMAKE_CURRENT_SOURCE_DIR})
message(${CMAKE_CURRENT_BINARY_DIR})

 


通过 message 将这些变量打印出来,对比看看,进入到 build 目录下,执行 cmake:

image

 

图 31.4-13 cmake 打印信息
 CMAKE_VERSION 、CMAKE_MAJOR_VERSION 和 CMAKE_MINOR_VERSION记录 cmake 的版本号,如下:

# src 下的 CMakeLists.txt
message(${CMAKE_VERSION})message(${CMAKE_MAJOR_VERSION})
message(${CMAKE_MINOR_VERSION})

 


打印信息如下:

image

 

图 31.4-14 cmake 打印信息
 PROJECT_VERSION 、PROJECT_VERSION_MAJOR 和 和 PROJECT_VERSION_MINOR记录工程的版本号,其实可以给工程设置一个版本号,通过 project()命令进行设置,如下:

# src 下的 CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project(HELLO VERSION 1.1.0) #设置工程版本号为 1.1.0
# 打印
message(${PROJECT_VERSION})
message(${PROJECT_VERSION_MAJOR})
message(${PROJECT_VERSION_MINOR})

 


打印信息如下:

image

 

图 31.4-15 cmake 打印信息
 CMAKE_PROJECT_NAME 和 和 PROJECT_NAME
这俩是等价的,记录了工程的名字:

# src 下的 CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project(HELLO VERSION 1.1.0) #设置工程版本号为 1.1.0
# 打印工程名字
message(${CMAKE_PROJECT_NAME})
message(${PROJECT_NAME})

 


打印信息如下:

image

 

图 31.4-16 cmake 打印信息

 改变行为的变量
顾名思义,意味着这些变量可以改变某些行为,所以我们可以通过对这些变量进行设置以改变行为。

图 31.4-17 改变行为的变量
 BUILD_SHARED_LIB
对于 add_library()命令,当没有显式指定生成动态库时(SHARED 选项),默认生成的是静态库;其实我们可以通过 BUILD_SHARED_LIBS 变量来控制 add_library()命令的行为,当将变量设置为 on 时表示使能动态库,则 add_library()默认生成的便是动态库文件;当变量设置为 off 或未设置时,add_library()默认生成
的便是静态库文件。测试如下:
譬如工程目录结构如下所示:
├── build
├── CMakeLists.txt
├── hello
│ └── hello.c
└── world
└── world.c
顶层 CMakeLists.txt 文件如下所示:
# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project(HELLO VERSION 1.1.0)
set(BUILD_SHARED_LIBS on)
add_library(hello hello/hello.c)
add_library(world world/world.c)
进入到 build 目录下,执行 cmake、make 进行构建、编译,将会生成动态库文件 libhello.so、libworld.so。
 CMAKE_BUILD_TYPE
设置编译类型 Debug 或者 Release。debug 版会生成相关调试信息,可以使用 GDB 进行调试;release 不会生成调试信息:
# Debug 版本
set(CMAKE_BUILD_TYPE Debug)

# Release 版本
set(CMAKE_BUILD_TYPE Release)
关于这个 Debug 或者 Release 版本的问题,后续有机会再给大家进行专题介绍。
 CMAKE_SYSROOT
cmake 会将该变量传递给编译器--sysroot 选项,通常我们在设置交叉编译时会使用到,后面再说!
 CMAKE_INCLUDE_PATH
为 find_file()和 find_path()命令指定搜索路径的目录列表。这两个命令前面没给大家介绍,它们分别用于查找文件、路径,我们需要传入一个文件名,find_file()命令会将该文件的全路径返回给我们;而 find_path()命令则会将文件的所在目录返回给我们。
这 两 个 命 令 去 哪 找 文 件 呢 ? 也 就 是 通 过 CMAKE_INCLUDE_PATH 变 量 来 进 行 指 定 ,CMAKE_INCLUDE_PATH 指定了一个目录列表,find_file()、find_path()会去这个目录列表中查找文件。接下来我们进行测试。
譬如工程目录结构如下所示:
├── build
├── CMakeLists.txt
└── src
└── hello.c
顶层 CMakeLists.txt 文件内容如下:
# CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project(HELLO VERSION 1.1.0) #设置工程版本号为 1.1.0
find_file(P_VAR hello.c)
message(${P_VAR})
通过 find_file 命令查找 hello.c 文件,将路径信息记录在 P_VAR 变量中;现在我们没有设置CMAKE_INCLUDE_PATH 变量,看看能不能找到 hello.c 文件,cmake 打印信息如下:

image

 

图 31.4-18 cmake 打印信息
很明显提示没有找到,现在我们对 CMAKE_INCLUDE_PATH 变量进行设置,如下所示:
# CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project(HELLO VERSION 1.1.0) #设置工程版本号为 1.1.0
# 设置 CMAKE_INCLUDE_PATH 变量
set(CMAKE_INCLUDE_PATH ${PROJECT_SOURCE_DIR}/src)
# 查找文件
find_file(P_VAR hello.c)

message(${P_VAR})
此时打印信息为:

image

 

图 31.4-19 cmake 打印信息
这次就成功找到了 hello.c 文件,并将文件的全路径返回给我们。
 CMAKE_LIBRARY_PATH
指定 find_library()命令的搜索路径的目录列表。find_library()命令用于搜索库文件,find_library()将会从CMAKE_LIBRARY_PATH 变量设置的目录列表中进行搜索。
 CMAKE_MODULE_PATH
指定要由 include()或 find_package()命令加载的 CMake 模块的搜索路径的目录列表。
 CMAKE_INCLUDE_DIRECTORIES_BEFORE
这个变量在前面给大家提到过,它可以改变 include_directories()命令的行为。include_directories()命令默认情况下会将目录添加到列表的后面,如果将 CMAKE_INCLUDE_DIRECTORIES_BEFORE 设置为 on,则include_directories()命令会将目录添加到列表前面;同理若将CMAKE_INCLUDE_DIRECTORIES_BEFORE
设置为 off 或未设置该变量,include_directories()会将目录添加到列表后面。
 CMAKE_IGNORE_PATH
要被 find_program()、find_library()、find_file()和 find_path()命令忽略的目录列表。表示这些命令不会去
CMAKE_IGNORE_PATH 变量指定的目录列表中搜索。
 描述系统的变量
顾名思义,这些变量描述了系统相关的一些信息:
变量 说明
CMAKE_HOST_SYSTEM_NAME 运行 cmake 的操作系统的名称(其实就是 uname -s)
CMAKE_HOST_SYSTEM_PROCESSOR 运行 cmake 的操作系统的处理器名称(uname -p)
CMAKE_HOST_SYSTEM 运行 cmake 的操作系统(复合信息)
CMAKE_HOST_SYSTEM_VERSION 运行 cmake 的操作系统的版本号(uname -r)
CMAKE_HOST_UNIX 如果运行 cmake 的操作系统是 UNIX 和类 UNIX,则
该变量为 true,否则是空值
CMAKE_HOST_WIN32 如果运行 cmake 的操作系统是 Windows,则该变量
为 true,否则是空值
CMAKE_SYSTEM_NAME 目标主机操作系统的名称
CMAKE_SYSTEM_PROCESSOR 目标主机的处理器名称
CMAKE_SYSTEM 目标主机的操作系统(复合信息)
CMAKE_SYSTEM_VERSION 目标主机操作系统的版本号
ENV 用于访问环境变量
UNIX 与 CMAKE_HOST_UNIX 等价
WIN32 与 CMAKE_HOST_WIN32 等价
表 31.4.3 描述系统的变量

 CMAKE_HOST_SYSTEM_NAME 、 CMAKE_HOST_SYSTEM_PROCESSOR 、
CMAKE_HOST_SYSTEM 和 和 CMAKE_HOST_SYSTEM_VERSION
这四个变量描述的是运行 cmake 的主机相关的信息,我们直接打印出来看看即可:
# CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project(HELLO VERSION 1.1.0) #设置工程版本号为 1.1.0
# 打印信息
message(${CMAKE_HOST_SYSTEM_NAME})
message(${CMAKE_HOST_SYSTEM_PROCESSOR})
message(${CMAKE_HOST_SYSTEM})
message(${CMAKE_HOST_SYSTEM_VERSION})
对应的打印信息如下:

image

 

图 31.4-20 cmake 打印信息
大家自己对照一看就知道了,笔者就不再多说了。
 CMAKE_SYSTEM_NAME 、 、 CMAKE_SYSTEM_PROCESSOR 、 、 CMAKE_SYSTEM 和
CMAKE_SYSTEM_VERSION
这 4 个变量则是用于描述目标主机相关的信息,目标主机指的是可执行文件运行的主机,譬如我们的ARM 开发板。
# CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project(HELLO VERSION 1.1.0) #设置工程版本号为 1.1.0
# 打印信息
message(${CMAKE_SYSTEM_NAME})
message(${CMAKE_SYSTEM_PROCESSOR})
message(${CMAKE_SYSTEM})
message(${CMAKE_SYSTEM_VERSION})
cmake 打印信息如下:

image

 

图 31.4-21 cmake 打印信息

因为我们并没有对 cmake 配置交叉编译,默认会使用 Ubuntu 系统(运行 cmake 的主机)本身的编译工具,所以生成的目标文件(可执行文件或库文件)只能运行在 Ubuntu 系统中,所以这 4 个变量记录的依然是 Ubuntu 主机的信息。
 ENV
这个变量可用于访问环境变量,用法很简单$ENV{VAR}
# CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")
project(HELLO VERSION 1.1.0) #设置工程版本号为 1.1.0
# 访问环境变量
message($ENV{XXX})
通过$ENV{XXX}访问 XXX 环境变量,我们来测试一下,首先在 Ubuntu 系统下使用 export 命令导出XXX 环境变量:
export XXX="Hello World!"
cd build/
cmake ..
打印信息如下所示:

image

 

图 31.4-22 cmake 打印信息
从打印信息可知,ENV 变量确实可以访问到 Linux 系统的环境变量。
 控制编译的变量
这些变量可以控制编译过程,具体如下所示:
变量 说明
EXECUTABLE_OUTPUT_PATH 可执行程序的输出路径
LIBRARY_OUTPUT_PATH 库文件的输出路径
表 31.4.4 控制编译的变量
这两个变量前面我们已经用到过了,分别用来设置可执行文件的输出目录以及库文件的输出目录,接下来我们进行简单地测试。
譬如工程目录结构如下所示:

├── build
├── CMakeLists.txt
├── hello
│ ├── hello.c
│ └── hello.h
└── main.c

 


hello.c 会被编译成动态库文件 libhello.so,而 main.c 会被编译成可执行程序,main.c 源码中调用了 hello.c提供的函数;顶层 CMakeLists.txt 文件内容如下所示:

# CMakeLists.txt
cmake_minimum_required("VERSION" "3.16")project(HELLO VERSION 1.1.0) #设置工程版本号为 1.1.0
# 设置可执行文件和库文件输出路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
# 头文件包含
include_directories(hello)
# 动态库目标
add_library(hello SHARED hello/hello.c)
# 可执行程序目标
add_executable(main main.c)
target_link_libraries(main PRIVATE hello) #链接库

 


进入到build目录下,执行cmake、make进行构建、编译,最终会生成可执行文件main和库文件libhello.so,
目录结构如下所示:

├── build
│ ├── bin
│ │ └── main
│ ├── lib
│ └── libhello.so
├── CMakeLists.txt
├── hello
│ ├── hello.c
│ └── hello.h
└── main.c

 


这是因为我们通过设置 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 才会使得生成的可执行程序在 build/bin 目录下、生成的库文件在 build/lib 目录下,如果把这两行给注释掉,那么生成的文件在 build 目录中,因为默认情况下,最终的目标文件的输出目录就是源码的 BINARY_DIR。
双引号的作用
CMake 中,双引号的作用我们可以从两个方面进行介绍,命令参数和引用变量。
命令参数
调用命令时,参数可以使用双引号,譬如:

project("HELLO")

 


也可以不使用双引号,譬如:

project(HELLO)

 


那它们有什么区别呢?在本例中是没有区别的,命令中多个参数之间使用空格进行分隔,而 cmake 会将双引号引起来的内容作为一个整体,当它当成一个参数,假如你的参数中有空格(空格是参数的一部分),
那么就可以使用双引号,如下所示:

message(Hello World)
message("Hello World")

 


在这个例子中,第一个 message 命令传入了两个参数,而第二个 message 命令只传入一个参数;在第一个 message 命令中,打印信息时,会将两个独立的字符串 Hello 和 World 都打印出来,而且 World 会紧跟在Hello 之后,如下:

HelloWorld

 


而第二个 message 命令只有一个参数,所以打印信息如下:

Hello World

 


这就是双引号在参数中的一个作用。
引用变量
我们先来看个例子,如下所示:

# CMakeLists.txt
set(MY_LIST Hello World China)
message(${MY_LIST})

 


这个例子的打印信息如下:

HelloWorldChina

 


在这个例子中,MY_LIST 是一个列表,该列表包含了 3 个元素,分别是 Hello、World、China。但这个message 命令打印时却将这三个元素全部打印出来,并且各个元素之间没有任何分隔。此时我们可以在引用变量(${MY_LIST})时加上双引号,如下所示:

# CMakeLists.txt
set(MY_LIST Hello World China)
message("${MY_LIST}")

 


此时 message 打印信息如下:

Hello;World;China

 


因为此时${MY_LIST}是一个列表,我们用"${MY_LIST}"这种形式的时候,表示要让 CMake 把这个数组的所有元素当成一个整体,而不是分散的个体。于是,为了保持数组的含义,又提供一个整体的表达方式,
CMake 就会用分号“;”把这数组的多个元素连接起来。
而如果不加双引号时,CMake 不会数组当成一个整体看待,而是会将数组中的各个元素提取出进行打印输出。
条件判断
在 cmake 中可以使用条件判断,条件判断形式如下:

if(expression)
# then section.
command1(args ...)
command2(args ...)
...
elseif(expression2)
# elseif section.
command1(args ...)
command2(args ...)
...
else(expression)
# else section.
command1(args ...)
command2(args ...)
...
endif(expression)

 


else 和 endif 括号中的<expression>可写可不写,如果写了,就必须和 if 中的<expression>一致。
expression 就是一个进行判断的表达式,表达式对照表如下:

表达式 true false 说明
<constant> 如果constant为1、ON、
YES、TRUE、Y 或非零
数,则为真
如果constant为0、
OFF 、 NO 、
FALSE 、 N 、
IGNORE 、
NOTFOUND、空
字符串或以后缀-
NOTFOUND 结
尾,则为 False。
布尔值大小
写不敏感;如
果与这些常
量都不匹配,
则将其视为
变量或字符
串
<variable|string> 已 经 定 义 并 且 不 是
false 的变量
未 定 义 或 者 是
false 的变量
变量就是字
符串
NOT <expression> expression 为 false expression 为 true
<expr1> AND <expr2> expr1 和 expr2 同时为
true
expr1 和 expr2 至
少有一个为 false
<expr1> OR <expr2> expr1 和 expr2 至少有
一个为 true
expr1 和 expr2 都
是 false
COMMAND name name 是一个已经定义
的命令、宏或者函数
name 未定义
TARGET name name 是
add_executable() 、
add_library() 或
add_custom_target() 定
义的目标
name 未定义
TEST name name 是由 add_test()命
令创建的现有测试名
称
name 未创建
EXISTS path path 指定的文件或目
录存在
path 指定的文件
或目录不存在
仅适用于完
整路径
IS_DIRECTORY path path 指定的路径为目录
path 指定的路径
不为目录
仅适用于完
整路径
IS_SYMLINK path path 为符号链接 path 不是符号链接
仅适用于完
整路径
IS_ABSOLUTE path path 为绝对路径 path 不是绝对路径
<variable|string> MATCHES regex variable 与正则表达式regex 匹配成功
variable 与正则表
达式 regex 匹配失
败<variable|string> IN_LIST <variable> 右边列表中包含左边
的元素
右边列表中不含
左边的元素
DEFINED <variable> 如果给定的变量已定
义,则为真。
如果给定的变量
未定义
只要变量已
经被设置,它
是真还是假
并不重要。
(注意宏不
是变量。)
<variable|string> LESS <variable|string> 如果给定的字符串或
变量的值是有效数字
且小于右侧的数字,则
为真。
左侧的数字大于
或等于右侧的数
字
<variable|string> GREATER <variable|string> 如果给定的字符串或
变量的值是有效数字
且大于右侧的数字,则
为真。
左侧的数字小于
或等于右侧的数
字
<variable|string> EQUAL <variable|string> 如果给定的字符串或
变量的值是有效数字
并且等于右侧的值,则
为真
左侧的数字不等
于右侧的数字

 


表 31.4.5 if 条件判断<表达式>对照表
上表中只是列出其中一部分表达式,还有其它一些表达式这里并未列出,大家可以通过
https://cmake.org/cmake/help/v3.5/command/if.html 这个链接地址进行查看,现在我们对上表中的表达式进行详解。
 <constant>
在 if(constant)条件判断中,如果 constant 是 1、ON、YES、TRUE、Y 或非零数字,那么这个 if 条件就是 true;如果 constant 是 0、OFF、NO、FALSE、N、IGNORE、NOTFOUND、空字符串或以后缀-NOTFOUND结尾,那么这个条件判断的结果就是 false。
在 cmake 中,可以把 1、ON、YES、TRUE、Y 或非零数字以及 0、OFF、NO、FALSE、N、IGNORE、NOTFOUND、空字符串或以后缀-NOTFOUND 结尾这些理解为常量,类似于布尔值,而且它们不区分大小写;如果参数不是这些特定常量之一,则将其视为变量或字符串,并使用除<constant>之外的表达式。

if(ON)
message(true)
else()
message(false)
endif()
输出为:true
if(YES)
message(true)
else()
message(false)
endif()
输出为:trueif(true)
message(true)
else()
message(false)
endif()
输出为:true
if(100)
message(true)
else()
message(false)
endif()
输出为:true
if(0)
message(true)
else()
message(false)
endif()
输出为:false
if(N)
message(true)
else()
message(false)
endif()
输出为:false
if(NO)
message(true)
else()
message(false)
endif()
输出为:false

 


 <variable/string>
在 if(<variable/string>)条件判断中,如果变量已经定义,并且它的值是一个非假常量,则条件为真;否
则为假,注意宏参数不是变量(在 cmake 中也可以使用宏,这个后面再给大家介绍)。

set(GG Hello)
if(GG)
message(true)
else()
message(false)
endif()
输出为:true
set(GG NO)
if(GG)
message(true)else()
message(false)
endif()
输出为:false
if(GG)
message(true)
else()
message(false)
endif()
输出为:false

 


 NOT <expression>
NOT 其实就类似于 C 语言中的取反,在 if(NOT <expression>)条件判断中,如果表达式 expression 为真,则条件判断为假;如果表达式 expression 为假,则条件判断为真。

if(NOT GG)
message(true)
else()
message(false)
endif()

 


输出为:true
因为 GG 变量没有定义,所以 GG 表达式为假,但因为前面有 NOT 关键字,进行取反操作,整个 if 条件判断为真。

if(NOT YES)
message(true)
else()
message(false)
endif()

 


输出为:false

if(NOT 0)
message(true)
else()
message(false)
endif()

 


输出为:true
 <expr1> AND <expr2>
这个就类似于 C 语言中的逻辑与(&&),只有 expr1 和 expr2 同时为真时,条件判断才为真;否则条件判断为假。

if(yes AND on)
message(true)
else()
message(false)
endif()

 


输出为:true

if(yes AND no)
message(true)
else()
message(false)
endif()

 



输出为:false

if(false AND no)
message(true)
else()
message(false)
endif()

 


输出为:false
 <expr1> OR <expr2>
类似于 C 语言中的逻辑或(||),当 expr1 或 expr2 至少有一个为真时,条件判断为真;否则为假。
if(false OR no)
message(true)
else()
message(false)
endif()
输出为:false
if(yes OR no)
message(true)
else()
message(false)
endif()
输出为:true
if(ON OR yes)
message(true)
else()
message(false)
endif()
输出为:true



 COMMAND command-name
如果 command-name 是一个已经定义的命令、宏或函数时,条件判断为真;否则为假。
除了宏之外,在 cmake 中还可以定义函数,这个我们也会在后面向大家介绍。
if(COMMAND yyds)
message(true)
else()
message(false)
endif()
输出为:false
if(COMMAND project)

message(true)
else()
message(false)
endif()
输出为:true



 TARGET target-name
如果 target-name 是 add_executable()、add_library()或 add_custom_target()定义的目标(这些目标在整个工程中必须是唯一的,不可出现两个名字相同的目标),则条件判断为真;否则为假。
if(TARGET hello)
message(true)
else()
message(false)
endif()
输出为:false
add_library(hello hello.c)
if(TARGET hello)
message(true)
else()
message(false)
endif()
输出为:true



 EXISTS path
如果 path 指定的文件或目录存在,则条件判断为真;否则为假。需要注意的是,path 必须是文件或目录的全路径,也就是绝对路径。
譬如工程目录结构如下所示:
├── build
├── CMakeLists.txt
├── hello
│ ├── hello.c
│ └── hello.h
└── main.c
在顶层 CMakeLists.txt 文件中使用 if(EXISTS path)进行判断:
if(EXISTS ${PROJECT_BINARY_DIR})
message(true)
else()
message(false)
endif()
输出为:true
if(EXISTS ${PROJECT_BINARY_DIR}/hello)
message(true)
else()

message(false)
endif()
输出为:true
if(EXISTS ${PROJECT_BINARY_DIR}/world)
message(true)
else()
message(false)
endif()
输出为:false
if(EXISTS ${PROJECT_BINARY_DIR}/hello/hello.c)
message(true)
else()
message(false)
endif()
输出为:true



 IS_DIRECTORY path
如果 path 指定的路径是一个目录,则条件判断为真;否则为假,同样,path 也必须是一个绝对路径。
还是以上例中的工程目录结构为例:
if(IS_DIRECTORY ${PROJECT_BINARY_DIR}/hello)
message(true)
else()
message(false)
endif()
输出为:true
if(IS_DIRECTORY ${PROJECT_BINARY_DIR}/hello/hello.c)
message(true)
else()
message(false)
endif()
输出为:true



 IS_ABSOLUTE path
如果给定的路径 path 是一个绝对路径,则条件判断为真;否则为假。
if(IS_ABSOLUTE ${PROJECT_BINARY_DIR})
message(true)
else()
message(false)
endif()
输出为:true
if(IS_ABSOLUTE ./hello)
message(true)
else()
message(false)
endif()
输出为:false



 <variable|string> MATCHES regex
这个表达式用的比较多,可以用来匹配字符串,可以使用正则表达式进行匹配。
如果给定的字符串或变量的值与给定的正则表达式匹配,则为真,否则为假。
set(MY_STR "Hello World")
if(MY_STR MATCHES "Hello World")
message(true)
else()
message(false)
endif()
输出为:true
其实也可以引用变量:
set(MY_STR "Hello World")
if(${MY_STR} MATCHES "Hello World")
message(true)
else()
message(false)
endif()
输出为:true
set(MY_STR "Hello World")
if("Hello World" MATCHES "Hello World")
message(true)
else()
message(false)
endif()
输出为:true



 <variable|string> IN_LIST <variable>
如果左边给定的变量或字符串是右边列表中的某个元素相同,则条件判断为真;否则为假。
set(MY_LIST Hello World China)
if(Hello IN_LIST MY_LIST)
message(true)
else()
message(false)
endif()
输出为:true
set(MY_LIST Hello World China)
set(Hello China)

if(Hello IN_LIST MY_LIST)
message(true)
else()
message(false)
endif()
输出为:true



 DEFINED <variable>
如果给定的变量已经定义,则条件判断为真,否则为假;只要变量已经被设置(定义),if 条件判断就是真,至于变量的值是真还是假并不重要。
if(DEFINED yyds)
message(true)
else()
message(false)
endif()
输出为:false
set(yyds "YYDS")
if(DEFINED yyds)
message(true)
else()
message(false)
endif()
输出为:true



 <variable|string> LESS <variable|string>
如果左边给定的字符串或变量的值是有效数字并且小于右侧的值,则为真。否则为假。
测试如下:
if(100 LESS 20)
message(true)
else()
message(false)
endif()
输出为:false
if(20 LESS 100)
message(true)
else()
message(false)
endif()
输出为:true



 <variable|string> GREATER <variable|string>
如果左边给定的字符串或变量的值是有效数字并且大于右侧的值,则为真。否则为假。
测试如下:

if(20 GREATER 100)
message(true)
else()
message(false)
endif()
输出为:false
if(100 GREATER 20)
message(true)
else()
message(false)
endif()
输出为:true



 <variable|string> EQUAL <variable|string>
如果左边给定的字符串或变量的值是有效数字并且等于右侧的值,则为真。否则为假。
测试如下:
if(100 EQUAL 20)
message(true)
else()
message(false)
endif()
输出为:false
if(100 EQUAL 100)
message(true)
else()
message(false)
endif()
输出为:true



 elseif 分支
可以使用 elseif 组成多个不同的分支:
set(MY_LIST Hello World China)
if(Hello IN_LIST MY_LIST)
message(Hello)
elseif(World IN_LIST MY_LIST)
message(World)
elseif(China IN_LIST MY_LIST)
message(China)
else()
message(false)
endif()


 


循环语句
cmake 中除了 if 条件判断之外,还支持循环语句,包括 foreach()循环、while()循环。
一、foreach 循环
①、 、foreach 基本用法
foreach 循环的基本用法如下所示:

foreach(loop_var arg1 arg2 ...)
command1(args ...)
command2(args ...)
...
endforeach(loop_var)

 


endforeach 括号中的<loop_var>可写可不写,如果写了,就必须和 foreach 中的<loop_var>一致。
参数 loop_var 是一个循环变量,循环过程中会将参数列表中的变量依次赋值给他,类似于 C 语言 for 循环中经常使用的变量 i。

# foreach 循环测试
foreach(loop_var A B C D)
message("${loop_var}")
endforeach()

 


打印信息为:
A
B
C
D
使用 foreach 可以编译一个列表中的所有元素,如下所示:

# foreach 循环测试
set(my_list hello world china)
foreach(loop_var ${my_list})
message("${loop_var}")
endforeach()

 


打印信息如下:

image

 

图 31.4-23 cmake 打印信息



②、foreach 循环之 RANGE 关键字
用法如下所示:

foreach(loop_var RANGE stop)
foreach(loop_var RANGE start stop [step])

 


对于第一种方式,循环会从 0 到指定的数字 stop,包含 stop,stop 不能为负数。

而对于第二种,循环从指定的数字 start 开始到 stop 结束,步长为 step,不过 step 参数是一个可选参数,
如果不指定,默认 step=1;三个参数都不能为负数,而且 stop 不能比 start 小。
接下来我们进行测试,测试一:

# foreach 循环测试
foreach(loop_var RANGE 4)
message("${loop_var}")
endforeach()

 


打印信息如下:

image

 

图 31.4-24 cmake 打印信息
测试二:

# foreach 循环测试
foreach(loop_var RANGE 1 4 1)
message("${loop_var}")
endforeach()

 


打印信息如下:

image

 

图 31.4-25 cmake 打印信息
③、foreach 循环之 IN 关键字
用法如下:

foreach(loop_var IN [LISTS [list1 [...]]]
[ITEMS [item1 [...]]])

 


循环列表中的每一个元素,或者直接指定元素。
接下来进行测试,测试一:

# foreach 循环测试
set(my_list A B C D)
foreach(loop_var IN LISTS my_list)
message("${loop_var}")
endforeach()

 


打印信息如下:

image

 

图 31.4-26 cmake 打印信息
测试二:

# foreach 循环测试
foreach(loop_var IN ITEMS A B C D)
message("${loop_var}")
endforeach()

 


打印信息同上。
二、while 循环
while 循环用法如下:

while(condition)
command1(args ...)
command1(args ...)
...
endwhile(condition)

 


endwhile 括号中的 condition 可写可不写,如果写了,就必须和 while 中的 condition 一致。
cmake 中 while 循环的含义与 C 语言中 while 循环的含义相同,但条件 condition 为真时,执行循环体中的命令,而条件 condition 的语法形式与 if 条件判断中的语法形式相同。

# while 循环测试
set(loop_var 4)
while(loop_var GREATER 0)
message("${loop_var}")
math(EXPR loop_var "${loop_var} - 1")
endwhile()

 


输出结果如下:

image

 

图 31.4-27 cmake 打印信息
上例中,while 循环的条件是(loop_var GREATER 0),等价于(loop_var > 0),当 loop_var 变量的有效数值大于 0 时,执行 while 循环体;在 while 循环体中使用到了 cmake 中的数学运算命令 math(),关于数学运算下小节会向大家介绍。
在 while 循环体中,打印 loop_var,之后将 loop_var 减一。



三、break 、continue
cmake 中,也可以在循环体中使用类似于 C 语言中的 break 和 continue 语句。



①、 、break
break()命令用于跳出循环,和在 C 语言中的作用是一样的,测试如下:

# while...break 测试
set(loop_var 10)
while(loop_var GREATER 0) #loop_var>0 时 执行循环体
message("${loop_var}")
if(loop_var LESS 6) #当 loop_var 小于 6 时
message("break")
break() #跳出循环
endif()
math(EXPR loop_var "${loop_var} - 1")#loop_var--
endwhile()

 


打印信息如下:

image

 

图 31.4-28 cmake 打印信息
整个代码笔者就不再解释了,注释已经写得很清楚了!

②、 、continue

 


continue()命令用于结束本次循环,执行下一次循环,测试如下:

# while...continue 测试
# 打印所有偶数
set(loop_var 10)
while(loop_var GREATER 0) #loop_var>0 时 执行循环体
math(EXPR var "${loop_var} % 2") #求余
if(var EQUAL 0) #如果 var=0,表示它是偶数
message("${loop_var}") #打印这个偶数
math(EXPR loop_var "${loop_var} - 1")#loop_var--
continue() # 执行下一次循环
endif()math(EXPR loop_var "${loop_var} - 1")#loop_var--
endwhile()

 


这段 cmake 代码是求 0 到 10 之间的偶数(左闭右开),并将偶数打印出来,使用到了 continue()命令,
代码不再解释,注释已经写得很清楚了。
打印结果如下:

image

 


图 31.4-29 cmake 打印信息
关于 break()和 continue()命令的使用就介绍到这里了。



数学运算 math
在 cmake 中如何使用数学运算呢?其实,cmake 提供了一个命令用于实现数学运算功能,这个命令就是 math(),如下所示:
math(EXPR <output variable> <math expression>)
math 命令中,第一个参数是一个固定的关键字 EXPR,第二个参数是一个返回参数,将数学运算结果存放在这个变量中;而第三个参数则是一个数学运算表达式,支持的运算符包括:+(加)、- (减)、*(乘)、/(除)、%(求余)、|(按位或)、&(按位与)、^(按位异或)、~(按位取反)、<<(左移)、>>(右
移)以及这些运算符的组合运算,它们的含义与 C 语言中相同。
譬如:

math(EXPR out_var "1+1") #计算 1+1
math(EXPR out_var "100 * 2") ##计算 100x2
math(EXPR out_var "10 & 20") #计算 10 & 20

 


我们进行测试:

# math()命令测试
math(EXPR out_var "100 + 100")
message("${out_var}")
math(EXPR out_var "100 - 50")
message("${out_var}")
math(EXPR out_var "100 * 100")
message("${out_var}")
math(EXPR out_var "100 / 50")
message("${out_var}")
math(EXPR out_var "(100 & 100) * 50 - 2")
message("${out_var}")

 



测试结果如下:

image

 

图 31.4-30 cmake 打印信息


 

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询