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.5")
project("HELLO")
message(${HELLO_SOURCE_DIR})
message(${HELLO_BINARY_DIR})
进入 build 目录下,执行 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.5")
project("HELLO")
# set 命令
set(VAR1 Hello) #设置变量 VAR1=Hello
set(VAR2 World) #设置变量 VAR2=World
# 打印变量
message(${VAR1} " " ${VAR2})
对应的打印信息:
字符串列表
通过 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 命令打印信息如下:
乍一看这个打印信息你是不是觉得 SRC_LIST 就是一个普通的变量(SRC_LIST= 1.c2.c3.c4.c5.c),并不是列表呢?事实并非如此,我们可以修改 message 命令,将${SRC_LIST}放置在双引号中,如下:
# 打印变量
message("${SRC_LIST}")
再次执行 cmake,打印信息如下:
可以看到此时打印出来的确实是一个列表,既然是列表,那自然可以使用 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 打印信息如下:
除此之外,在 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>...]...)
这两个命令都有一个相同的参数目标,这个目标指的就是譬如 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 来说,这些关键字用于指示何时需要传递给目标的包含目录列表,指定了包含目录列表的使用范围:
⚫ 当使用 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(),保持整个工程的目录清晰。
总结
关于cmake常用命令、变量的学习到此结束了,给大家介绍了一些基本、常用的命令,并进行了详细的解释说明,除此之外,还有很多的命令并未提及,将会在后面介绍。