CMake 基本语法规则
1. 注释
在 CMakeLists.txt 文件中,使用 # 号进行单行注释:
cmake
#
# 这是注释信息
#
cmake_minimum_required(VERSION 3.5)
project(HELLO)
2. 命令(command)
命令的使用方式类似于 C 语言中的函数,命令后面需要提供一对括号,并且通常需要提供参数,多个参数使用空格分隔 而不是逗号 ,。
语法格式:
cmake
command(参数1 参数2 参数3 ...)
- 必要参数 :使用
<参数>表示 - 可选参数(选项) :使用
[参数]表示
示例(set 命令):
cmake
set(<variable> <value>... [PARENT_SCOPE])
命令名不区分大小写,可以使用大写或小写字母书写命令名:
cmake
project(HELLO) # 小写
PROJECT(HELLO) # 大写
两者效果相同。通常建议使用小写字母,以便与 CMake 内置变量(通常全大写)区分开来。
3. 变量(variable)
使用 set 命令设置变量:
cmake
set(MY_VAL "Hello World!")
引用变量使用 ${变量名} 方式:
cmake
set(MY_VAL "Hello World!")
message(${MY_VAL})
变量分为 CMake 内置变量 (如 LIBRARY_OUTPUT_PATH、EXECUTABLE_OUTPUT_PATH 等)和 自定义变量。
4. 常用命令详解
关于"源码"的说明 :在 CMake 中,"当前源码路径"指的是
CMakeLists.txt文件所在的目录,因为CMakeLists.txt被称为 cmake 的源码。若无特别说明,后续提到的"当前源码路径"均指当前CMakeLists.txt文件所在目录。
cmake 提供了很多命令,可以通过 cmake 官方文档 查询所有命令的详细介绍和使用方法。下面是常用基本命令的汇总:
| 命令 | 说明 |
|---|---|
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 |
设置指定目标所需的源文件 |
4.1 add_executable
add_executable 命令用于添加一个可执行程序目标,并设置目标所需的源文件,命令定义如下:
cmake
add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
该命令提供了一些可选参数(WIN32、MACOSX_BUNDLE、EXCLUDE_FROM_ALL),通常不需要加入,其具体含义可以查看 cmake 官方文档。只需传入目标名和对应的源文件即可。
示例:
cmake
# 生成可执行文件 hello
add_executable(hello 1.c 2.c 3.c)
定义了一个可执行程序目标 hello,生成该目标文件所需的源文件为 1.c、2.c 和 3.c。源文件路径既可以使用相对路径,也可以使用绝对路径。相对路径被解释为相对于当前源码路径。
4.2 add_library
添加一个库文件目标,并设置目标所需的源文件:
cmake
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
- 默认生成静态库文件
- 通过
SHARED选项可生成动态库文件
示例:
cmake
# 生成静态库文件 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所定义的目标名在整个工程中必须是唯一的,不可出现同名目标。
4.3 add_subdirectory
add_subdirectory 命令告诉 cmake 去指定的目录中寻找源码(CMakeLists.txt)并执行它,类似于 Makefile 的 include,命令定义如下:
cmake
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
source_dir:指定一个目录,告诉 cmake 去该目录下寻找CMakeLists.txt文件并执行它binary_dir:指定子源码的输出文件目录(可选)。调用add_subdirectory的源码称为当前源码(父源码) ,被执行的源码称为子源码 。binary_dir作为子源码的输出文件(cmake 所产生的中间文件)目录,如果没有显式指定,则会使用一个默认的输出文件目录。为了便于表述,将输出文件目录称为 BINARY_DIR
相对路径解释 :binary_dir 的相对路径是相对于当前源码的 BINARY_DIR,而非当前源码路径。
示例 1 --- 加载子目录(不指定 binary_dir):
目录结构:
├── build
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.c
顶层 CMakeLists.txt:
cmake
cmake_minimum_required("VERSION" "3.5")
project("HELLO")
# 告诉 cmake 去 src 目录下寻找 CMakeLists.txt
add_subdirectory(src)
src/CMakeLists.txt:
cmake
add_executable(hello main.c)
进入 build 目录执行 cmake ..; make 后,顶层源码对应的输出文件会存放在 build 目录(即执行 cmake 的目录);子源码对应的输出文件会存放在 build/src 目录,包括生成的可执行文件默认会与这些中间文件放置在同一个目录:
├── build
│ ├── CMakeCache.txt
│ ├── CMakeFiles
│ ├── cmake_install.cmake
│ ├── Makefile
│ └── src
│ ├── CMakeFiles
│ ├── cmake_install.cmake
│ ├── hello
│ └── Makefile
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.c
由此可见,若没有为子源码指定 BINARY_DIR,默认情况下会在当前源码的 BINARY_DIR 中创建与子目录同名的文件夹,将其作为子源码的 BINARY_DIR。
示例 2 --- 显式指定 binary_dir:
cmake
cmake_minimum_required("VERSION" "3.5")
project("HELLO")
# 指定子源码的 BINARY_DIR 为 output
add_subdirectory(src output)
此时子源码的输出文件会生成在 build/output 目录下,而非默认的 build/src。设置 BINARY_DIR 可以使用相对路径(相对于当前源码的 BINARY_DIR)或绝对路径。
示例 3 --- 加载平级目录(必须显式指定 binary_dir):
通过 add_subdirectory 加载的目录既可以是当前源码路径的子目录,也可以是平级目录或上级目录。对于当前源码路径的子目录,不强制显式指定 BINARY_DIR;但如果不是当前源码路径的子目录,则必须显式指定 BINARY_DIR,否则会报错。
目录结构:
├── build
├── CMakeLists.txt
├── lib
│ └── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.c
src/CMakeLists.txt 中加载平级目录 lib 时若未指定 BINARY_DIR:
cmake
add_subdirectory(../lib) # 错误!会报错
必须显式指定:
cmake
add_subdirectory(../lib output)
4.4 aux_source_directory
aux_source_directory 命令会查找指定目录中的所有源文件,并将扫描到的源文件路径信息存放到变量中,命令定义如下:
cmake
aux_source_directory(<dir> <variable>)
示例(目录结构如下):
├── build
├── CMakeLists.txt
└── src
├── 1.c
├── 2.c
├── 2.cpp
└── main.c
cmake
cmake_minimum_required("VERSION" "3.5")
project("HELLO")
# 查找 src 目录下的所有源文件
aux_source_directory(src SRC_LIST)
message("${SRC_LIST}") # 打印 SRC_LIST 变量
执行 cmake 后,aux_source_directory 会将扫描到的每一个源文件路径添加到 SRC_LIST 变量中,组成一个字符串列表,各元素使用分号 ; 分隔。例如执行 cmake .. 后,打印信息如下:
src/1.c;src/2.c;src/2.cpp;src/main.c
路径既可以使用相对路径,也可以使用绝对路径,相对路径是相对于当前源码路径。
4.5 get_target_property 和 set_target_properties
分别用于获取和设置目标的属性,将在后续介绍。
4.6 include_directories
include_directories 命令用于设置头文件的搜索路径,相当于 gcc 的 -I 选项,命令定义如下:
cmake
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
默认情况下会将指定目录添加到头文件搜索列表(可以认为每一个 CMakeLists.txt 源码都有自己的头文件搜索列表)的最后面。可以通过以下方式改变行为:
- 设置
CMAKE_INCLUDE_DIRECTORIES_BEFORE变量为ON,使默认添加位置改为列表前面 - 使用
AFTER或BEFORE选项显式指定添加到列表后面或前面 - 使用
SYSTEM选项将指定目录视为系统的搜索目录
支持绝对路径和相对路径,相对路径被解释为当前源码路径的相对路径。
示例(目录结构如下):
├── build
├── CMakeLists.txt
├── include
│ └── hello.h
└── main.c
cmake
cmake_minimum_required("VERSION" "3.5")
project("HELLO")
include_directories(include)
add_executable(hello main.c)
如果不加 include_directories(include) 这条命令,编译时会报错提示找不到头文件。
使用 AFTER 或 BEFORE 选项:
cmake
# 添加到列表后面
include_directories(AFTER include)
# 添加到列表前面
include_directories(BEFORE include)
继承性 :当调用 add_subdirectory 加载子源码时,会将 include_directories 包含的目录列表向下传递给子源码(子源码从父源码中继承过来)。
4.7 link_directories 和 link_libraries
link_directories:设置库文件的搜索路径,相当于 gcc 的-L选项link_libraries:设置需要链接的库文件,相当于 gcc 的-l选项
cmake
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
cmake
cmake_minimum_required("VERSION" "3.5")
project("HELLO")
include_directories(include)
link_directories(lib)
link_libraries(hello)
add_executable(main main.c)
库文件名既可以使用简写,也可以使用库文件名的全称:
cmake
# 简写
link_libraries(hello)
# 全称
link_libraries(libhello.so)
也可以指定库文件的全路径(绝对路径,以 / 开头)。如果不是以 / 开头,link_libraries 会认为传入的是库文件名,而非全路径:
cmake
link_libraries(${PROJECT_SOURCE_DIR}/lib/libhello.so)
与 include_directories 相同,当调用 add_subdirectory 加载子源码时,会将 link_directories 包含的目录列表以及 link_libraries 包含的链接库列表向下传递给子源码(子源码从父源码中继承过来)。
4.8 list
list 命令是一个关于列表操作的命令,列表其实就是字符串数组(也叫字符串列表)。命令定义如下:
cmake
list(LENGTH <list> <output variable>) # 返回列表长度
list(GET <list> <element index> [...] <output variable>) # 从列表中返回由索引值指定的元素
list(APPEND <list> [<element> ...]) # 将元素追加到列表后面
list(FIND <list> <value> <output variable>) # 返回指定元素的索引值,未找到返回 -1
list(INSERT <list> <element_index> <element> [...]) # 向列表中的指定位置插入元素
list(REMOVE_ITEM <list> <value> [...]) # 删除给定的元素(按值删除)
list(REMOVE_AT <list> <index> [...]) # 删除给定索引值的元素(按索引删除)
list(REMOVE_DUPLICATES <list>) # 删除列表中的重复元素
list(REVERSE <list>) # 就地反转列表的内容
list(SORT <list>) # 按字母顺序对列表进行排序
示例:
cmake
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 后打印信息如下:
SRC_LIST: main.c;world.c;hello.c
列表长度: 3
获取列表中 index=1 的元素: world.c
SRC_LIST: main.c;world.c;hello.c;hello_world.c
SRC_LIST: hello.c;hello_world.c;main.c;world.c
除此之外,还可以使用循环语句依次读取列表中的各个元素。
4.9 message
用于打印、输出信息,类似于 Linux 的 echo 命令:
cmake
message([<mode>] "message to display" ...)
mode 关键字说明:
| mode | 说明 |
|---|---|
| 无 | 重要信息、普通信息 |
STATUS |
附带信息 |
WARNING |
CMake 警告,继续处理 |
AUTHOR_WARNING |
CMake 警告(开发),继续处理 |
SEND_ERROR |
CMake 错误,继续处理,但跳过生成 |
FATAL_ERROR |
CMake 错误,停止处理和生成 |
DEPRECATION |
CMake 弃用错误或警告(取决于变量设置) |
示例:
cmake
message("Hello World!")
message(STATUS "This is a status message")
message(FATAL_ERROR "An error occurred, stop processing!")
4.10 project
project 命令用于设置工程名称:
cmake
project(HELLO)
执行 project(HELLO) 后会引入两个变量:HELLO_SOURCE_DIR 和 HELLO_BINARY_DIR。注意这两个变量名的前缀就是工程名称,其中 HELLO_SOURCE_DIR 变量指的是 HELLO 工程源码目录(顶层 CMakeLists.txt 所在目录),HELLO_BINARY_DIR 变量指的是 HELLO 工程源码的输出文件目录。如果不加入 project(HELLO) 命令,这两个变量是不存在的。
cmake 还定义了两个等价的变量 PROJECT_SOURCE_DIR 和 PROJECT_BINARY_DIR。在单工程(只有一个 project())的情况下,HELLO_SOURCE_DIR 与 PROJECT_SOURCE_DIR 指向同一目录。但如果子目录中又调用了另一个 project(),则 PROJECT_SOURCE_DIR 会变为最近一次 project() 所在目录,而 HELLO_SOURCE_DIR 始终指向 HELLO 工程的顶层目录,不会改变。因此,推荐使用 PROJECT_SOURCE_DIR 和 PROJECT_BINARY_DIR,这样不依赖具体工程名,写出的 CMakeLists.txt 更通用。
也可以同时设置版本号:
cmake
project(HELLO VERSION 1.1.0)
示例(打印工程信息):
cmake
cmake_minimum_required("VERSION" "3.5")
project("HELLO")
message(${HELLO_SOURCE_DIR})
message(${HELLO_BINARY_DIR})
进入 build 目录执行 cmake .. 后,打印信息示例:
/home/user/project/hello
/home/user/project/hello/build
通常只需在顶层 CMakeLists.txt 中调用 project 即可。
4.11 set
设置变量的值:
cmake
set(<variable> <value>... [PARENT_SCOPE])
可选参数 PARENT_SCOPE 影响变量的作用域。
示例:
cmake
set(VAR1 Hello)
set(VAR2 World)
message(${VAR1} " " ${VAR2})
执行 cmake 后打印信息如下:
Hello World
4.12 target_include_directories 和 target_link_libraries
target_include_directories 命令为指定目标 设置头文件搜索路径,而 target_link_libraries 命令为指定目标设置链接库文件。它们与 include_directories / link_libraries 的功能相同,但作用范围更精确------只针对指定的目标。
cmake
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 等命令所创建的目标。SYSTEM、BEFORE 选项与 include_directories 中的含义相同。
这里重点说明 INTERFACE|PUBLIC|PRIVATE 三个关键字的区别。假设有如下工程结构:
├── 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_world.so ──── libhello.so
└─── libworld.so
PRIVATE(私有的) :包含目录列表或链接库列表仅用于当前目标 ,不会传递给依赖该目标的其他目标。例如libhello_world.so内部使用了libhello.so,但对外头文件和main.c都不知道libhello.so的存在,则应使用PRIVATE。INTERFACE(接口) :包含目录列表或链接库列表不用于当前目标 ,但会传递给依赖该目标的其他目标。例如libhello_world.so内部不使用libhello.so的功能,但其对外头文件中引用了libhello.so的内容,main.c需要使用libhello.so的功能,则应使用INTERFACE。PUBLIC(公开的) :PUBLIC = PRIVATE + INTERFACE,既用于当前目标,也会传递给依赖目标。例如libhello_world.so内部和对外头文件都使用了libhello.so,则应使用PUBLIC。
示例(以下示例均以 hello_world 为目标,对应上面目录结构中的 hello_world/CMakeLists.txt):
cmake
# PRIVATE:libhello_world.so 内部使用了 libhello.so,但对外不暴露
# hello_world.c 中 #include "hello.h",但 hello_world.h 中不含 hello.h
# main.c 不需要知道 libhello.so 的存在
target_include_directories(hello_world PRIVATE hello)
target_link_libraries(hello_world PRIVATE hello)
cmake
# INTERFACE:libhello_world.so 内部未使用 libhello.so,但对外头文件中引用了它
# hello_world.c 不含 hello.h,但 hello_world.h 中 #include "hello.h"
# main.c 需要使用 libhello.so 提供的功能
target_include_directories(hello_world INTERFACE hello)
target_link_libraries(hello_world INTERFACE hello)
cmake
# PUBLIC:libhello_world.so 内部和对外头文件都使用了 libhello.so
# hello_world.c 和 hello_world.h 中都包含了 hello.h
# main.c 也需要使用 libhello.so 的功能
target_include_directories(hello_world PUBLIC hello)
target_link_libraries(hello_world PUBLIC hello)
为什么推荐使用 target_include_directories 和 target_link_libraries?
include_directories() 和 link_libraries() 是针对当前源码中的所有目标 ,并且会向下传递(例如通过 add_subdirectory 加载子源码时,也会将其传递给子源码)。在一个大的工程当中,这通常不规范,有时还会导致编译错误和混乱。所以应尽量使用 target_include_directories() 和 target_link_libraries(),保持整个工程的目录清晰。
5. 字符串列表
通过 set 命令可以创建字符串列表(也叫字符串数组):
cmake
# 字符串列表
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
引用列表时,双引号的作用至关重要:
cmake
cmake_minimum_required("VERSION" "3.5")
project("HELLO")
set(SRC_LIST 1.c 2.c 3.c 4.c 5.c)
# 不加双引号
message(${SRC_LIST})
执行 cmake 后打印信息如下:
1.c2.c3.c4.c5.c
乍一看似乎 SRC_LIST 就是一个普通变量(SRC_LIST=1.c2.c3.c4.c5.c),并不是列表。这是因为不加双引号时,cmake 会将列表中的各个元素提取出来直接拼接在一起输出。
将 ${SRC_LIST} 放在双引号中再次测试:
cmake
message("${SRC_LIST}")
此时打印信息如下:
1.c;2.c;3.c;4.c;5.c
可以看到这次打印出来的确实是一个列表,各元素以分号 ; 分隔。原因是使用 "${SRC_LIST}" 这种形式时,cmake 会把列表的所有元素当成一个整体,为了保持数组的含义,会用分号将多个元素连接起来。
引用列表时,使用 "${变量名}" 形式可以保持列表的分隔符(分号),而不加双引号时会将各元素直接拼接输出。
6. 常用变量
变量也是 cmake 中的一个重点,cmake 提供了很多内置变量,每一个变量都有它自己的含义。可以通过 cmake 官方文档 查询所有内置变量及其介绍。cmake 对变量进行了分类,包括:提供信息的变量 、改变行为的变量 、描述系统的变量 、控制编译的变量等。
6.1 提供信息的变量
顾名思义,这种变量可以提供某种信息,通常只需要读取变量即可,而不需要对变量进行修改。
| 变量 | 说明 |
|---|---|
PROJECT_SOURCE_DIR |
工程顶层目录(顶层 CMakeLists.txt 所在目录) |
PROJECT_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 等价 |
PROJECT_SOURCE_DIR 和 PROJECT_BINARY_DIR
PROJECT_SOURCE_DIR 变量表示工程的顶级目录,也就是顶层 CMakeLists.txt 文件所在目录;PROJECT_BINARY_DIR 变量表示工程的 BINARY_DIR,也就是顶层 CMakeLists.txt 源码对应的输出文件目录。
示例:
目录结构:
├── build
├── CMakeLists.txt
└── main.c
cmake
cmake_minimum_required("VERSION" "3.5")
project(HELLO)
message(${PROJECT_SOURCE_DIR})
message(${PROJECT_BINARY_DIR})
进入 build 目录执行 cmake .. 后打印信息如下:
/home/user/project/hello
/home/user/project/hello/build
可见 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
CMAKE_CURRENT_SOURCE_DIR 指的是当前正在被 cmake 处理的 CMakeLists.txt 所在的路径 ,CMAKE_CURRENT_BINARY_DIR 则是这个 CMakeLists.txt 对应的输出目录。当通过 add_subdirectory 加载子源码时,这两个变量的值会随之变化(指向子源码对应的路径),而 PROJECT_SOURCE_DIR 和 PROJECT_BINARY_DIR 始终指向顶层目录,不会改变。
示例:
目录结构:
├── build
├── CMakeLists.txt
├── main.c
└── src
└── CMakeLists.txt
顶层 CMakeLists.txt:
cmake
cmake_minimum_required("VERSION" "3.5")
project("HELLO")
add_subdirectory(src)
src/CMakeLists.txt:
cmake
message("PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
message("PROJECT_BINARY_DIR: ${PROJECT_BINARY_DIR}")
message("CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")
message("CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}")
在子源码 src/CMakeLists.txt 执行时,CMAKE_CURRENT_SOURCE_DIR 指向 src/ 目录,CMAKE_CURRENT_BINARY_DIR 指向 build/src/ 目录;而 PROJECT_SOURCE_DIR 和 PROJECT_BINARY_DIR 始终指向顶层目录(./ 和 build/),不会因为进入了子源码而改变。
CMAKE_VERSION、CMAKE_MAJOR_VERSION 和 CMAKE_MINOR_VERSION
记录 cmake 的版本号:
cmake
message(${CMAKE_VERSION})
message(${CMAKE_MAJOR_VERSION})
message(${CMAKE_MINOR_VERSION})
打印信息示例:
3.5.1
3
5
PROJECT_VERSION、PROJECT_VERSION_MAJOR 和 PROJECT_VERSION_MINOR
记录工程的版本号,通过 project() 命令设置版本号:
cmake
cmake_minimum_required("VERSION" "3.5")
project(HELLO VERSION 1.1.0)
message(${PROJECT_VERSION})
message(${PROJECT_VERSION_MAJOR})
message(${PROJECT_VERSION_MINOR})
打印信息如下:
1.1.0
1
1
CMAKE_PROJECT_NAME 和 PROJECT_NAME
这两个是等价的,记录了工程的名字:
cmake
cmake_minimum_required("VERSION" "3.5")
project(HELLO VERSION 1.1.0)
message(${CMAKE_PROJECT_NAME})
message(${PROJECT_NAME})
打印信息如下:
HELLO
HELLO
6.2 改变行为的变量
顾名思义,这些变量可以改变某些行为,所以可以通过对这些变量进行设置来改变行为。
| 变量 | 说明 |
|---|---|
BUILD_SHARED_LIBS |
控制 add_library() 默认是否生成动态库(设为 on 则默认生成动态库) |
CMAKE_BUILD_TYPE |
指定构建类型,Debug 或 Release |
CMAKE_SYSROOT |
对应编译器的 --sysroot 选项 |
CMAKE_IGNORE_PATH |
设置被 find_xxx 命令忽略的目录列表 |
CMAKE_INCLUDE_PATH |
为 find_file() 和 find_path() 指定搜索路径的目录列表 |
CMAKE_INCLUDE_DIRECTORIES_BEFORE |
控制 include_directories() 命令的行为(添加目录到列表前面或后面) |
CMAKE_LIBRARY_PATH |
指定 find_library() 的搜索路径目录列表 |
CMAKE_MODULE_PATH |
指定 include() 或 find_package() 加载的 CMake 模块搜索路径 |
CMAKE_PROGRAM_PATH |
指定 find_program() 的搜索路径目录列表 |
BUILD_SHARED_LIBS
对于 add_library() 命令,当没有显式指定生成动态库时(SHARED 选项),默认生成的是静态库。可以通过 BUILD_SHARED_LIBS 变量来控制 add_library() 命令的行为。当将变量设置为 on 时表示使能动态库,则 add_library() 默认生成的便是动态库文件;当变量设置为 off 或未设置时,add_library() 默认生成的便是静态库文件。
目录结构:
├── build
├── CMakeLists.txt
├── hello
│ └── hello.c
└── world
└── world.c
cmake
cmake_minimum_required("VERSION" "3.5")
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(而非默认的静态库 libhello.a、libworld.a)。
CMAKE_BUILD_TYPE
设置编译类型 Debug 或 Release。Debug 版会生成相关调试信息,可以使用 GDB 进行调试;Release 不会生成调试信息。
cmake
# Debug 版本
set(CMAKE_BUILD_TYPE Debug)
# Release 版本
set(CMAKE_BUILD_TYPE Release)
CMAKE_SYSROOT
cmake 会将该变量传递给编译器的 --sysroot 选项,通常用于交叉编译时设置。
CMAKE_INCLUDE_PATH
为 find_file() 和 find_path() 命令指定搜索路径的目录列表。这两个命令分别用于查找文件和路径:传入一个文件名,find_file() 会将该文件的全路径返回,find_path() 则会将文件的所在目录返回。它们通过 CMAKE_INCLUDE_PATH 变量来指定搜索目录。
目录结构:
├── build
├── CMakeLists.txt
└── src
└── hello.c
不设置 CMAKE_INCLUDE_PATH 时查找文件:
cmake
cmake_minimum_required("VERSION" "3.5")
project(HELLO VERSION 1.1.0)
find_file(P_VAR hello.c)
message(${P_VAR})
cmake 会提示找不到文件。设置 CMAKE_INCLUDE_PATH 后再次查找:
cmake
cmake_minimum_required("VERSION" "3.5")
project(HELLO VERSION 1.1.0)
set(CMAKE_INCLUDE_PATH ${PROJECT_SOURCE_DIR}/src)
find_file(P_VAR hello.c)
message(${P_VAR})
此时可以成功找到 hello.c 文件,并将文件的全路径返回。
CMAKE_LIBRARY_PATH
指定 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
set(CMAKE_INCLUDE_DIRECTORIES_BEFORE ON)
CMAKE_IGNORE_PATH
要被 find_program()、find_library()、find_file() 和 find_path() 命令忽略的目录列表。表示这些命令不会去 CMAKE_IGNORE_PATH 变量指定的目录列表中搜索。
6.3 描述系统的变量
顾名思义,这些变量描述了系统相关的一些信息。其中 CMAKE_HOST_* 描述的是运行 cmake 的主机 的信息(即当前正在编译的机器),而 CMAKE_*(无 HOST 前缀)描述的是目标主机的信息(即编译出的可执行文件将要运行的机器)。
| 变量 | 说明 |
|---|---|
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 |
若是 UNIX/类 UNIX 系统则为 true |
CMAKE_HOST_WIN32 |
若是 Windows 系统则为 true |
CMAKE_SYSTEM_NAME |
目标主机操作系统名称 |
CMAKE_SYSTEM_PROCESSOR |
目标主机处理器名称 |
CMAKE_SYSTEM |
目标主机操作系统(复合信息) |
CMAKE_SYSTEM_VERSION |
目标主机操作系统版本号 |
ENV |
用于访问环境变量(用法:$ENV{VAR}) |
UNIX |
与 CMAKE_HOST_UNIX 等价 |
WIN32 |
与 CMAKE_HOST_WIN32 等价 |
CMAKE_HOST_SYSTEM_NAME、CMAKE_HOST_SYSTEM_PROCESSOR、CMAKE_HOST_SYSTEM 和 CMAKE_HOST_SYSTEM_VERSION
这四个变量描述的是运行 cmake 的主机相关的信息:
cmake
message(${CMAKE_HOST_SYSTEM_NAME})
message(${CMAKE_HOST_SYSTEM_PROCESSOR})
message(${CMAKE_HOST_SYSTEM})
message(${CMAKE_HOST_SYSTEM_VERSION})
在 Ubuntu 系统上执行时打印信息示例:
Linux
x86_64
Linux-5.15.0
#1 SMP Ubuntu ...
CMAKE_SYSTEM_NAME、CMAKE_SYSTEM_PROCESSOR、CMAKE_SYSTEM 和 CMAKE_SYSTEM_VERSION
这四个变量描述的是目标主机 相关的信息。目标主机指的是可执行文件将要运行的主机(例如 ARM 开发板)。如果没有配置交叉编译,默认使用主机本身的编译工具,生成的目标文件只能运行在当前主机上,所以它们的值与 CMAKE_HOST_* 相同。
cmake
message(${CMAKE_SYSTEM_NAME})
message(${CMAKE_SYSTEM_PROCESSOR})
message(${CMAKE_SYSTEM})
message(${CMAKE_SYSTEM_VERSION})
在未配置交叉编译的情况下,打印信息与 CMAKE_HOST_* 一致。
ENV
用于访问系统的环境变量,用法为 $ENV{VAR}:
cmake
# 访问环境变量
message($ENV{XXX})
首先在终端中使用 export 命令导出 XXX 环境变量,然后执行 cmake:
bash
export XXX="Hello World!"
cd build/
cmake ..
打印信息如下:
Hello World!
从打印信息可知,ENV 变量确实可以访问到系统的环境变量。
CMAKE_HOST_UNIX 和 CMAKE_HOST_WIN32(以及等价的 UNIX 和 WIN32)
CMAKE_HOST_UNIX 在运行 cmake 的操作系统是 UNIX 或类 UNIX 系统时为 true,否则为空值;CMAKE_HOST_WIN32 在 Windows 系统上为 true,否则为空值。
cmake
if(UNIX)
message("This is a UNIX-like system")
endif()
if(WIN32)
message("This is a Windows system")
endif()
6.4 控制编译的变量
这些变量可以控制编译过程,用于设置输出文件的存放位置。
| 变量 | 说明 |
|---|---|
EXECUTABLE_OUTPUT_PATH |
可执行程序的输出路径 |
LIBRARY_OUTPUT_PATH |
库文件的输出路径 |
默认情况下,最终的目标文件(可执行文件和库文件)的输出目录就是源码的 BINARY_DIR。通过设置这两个变量,可以将不同类型的目标文件分别存放到不同的目录中。
示例(目录结构如下):
├── build
├── CMakeLists.txt
├── hello
│ ├── hello.c
│ └── hello.h
└── main.c
hello.c 会被编译成动态库文件 libhello.so,main.c 会被编译成可执行程序,main.c 中调用了 hello.c 提供的函数:
cmake
cmake_minimum_required("VERSION" "3.5")
project(HELLO VERSION 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 后,生成的文件目录结构如下:
├── build
│ ├── bin
│ │ └── main
│ ├── lib
│ │ └── libhello.so
├── CMakeLists.txt
├── hello
│ ├── hello.c
│ └── hello.h
└── main.c
可以看到,可执行文件 main 被输出到了 build/bin 目录下,库文件 libhello.so 被输出到了 build/lib 目录下。如果没有设置 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH,那么生成的目标文件会默认与中间文件一起放在 build/ 目录中。
7. 双引号的作用
CMake 中,双引号的作用可以从两个方面来理解:命令参数 和引用变量。
7.1 命令参数
调用命令时,参数可以使用双引号,也可以不使用。双引号会将引起来的内容作为一个整体参数。如果参数中包含空格(空格是参数的一部分),那么就必须使用双引号。
cmake
message(Hello World) # 传入两个参数:Hello 和 World
message("Hello World") # 只传入一个参数:Hello World
第一个 message 命令传入了两个参数(Hello 和 World),打印时会将两个字符串拼接在一起输出,World 紧跟在 Hello 之后:
HelloWorld
而第二个 message 命令只有一个参数,所以打印信息如下:
Hello World
这就是双引号在参数中的作用。
7.2 引用变量
先来看一个例子:
cmake
set(MY_LIST Hello World China)
message(${MY_LIST})
打印信息如下:
HelloWorldChina
在这个例子中,MY_LIST 是一个列表,包含了 3 个元素(Hello、World、China)。但 message 命令打印时将这 3 个元素全部打印出来,并且各个元素之间没有任何分隔。
此时在引用变量(${MY_LIST})时加上双引号:
cmake
message("${MY_LIST}")
打印信息如下:
Hello;World;China
因为此时 ${MY_LIST} 是一个列表,用 "${MY_LIST}" 这种形式时,表示要让 cmake 把这个列表的所有元素当成一个整体,而不是分散的个体。于是为了保持数组的含义,又提供一个整体的表达方式,cmake 会用分号 ; 把列表的多个元素连接起来。
而如果不加双引号时,cmake 不会把列表当成一个整体看待,而是会将列表中的各个元素提取出来直接拼接打印输出。
引用列表时,使用 "${变量名}" 形式可以保持列表的分隔符(分号),而不加双引号时会将各元素直接拼接输出。
8. 条件判断
在 cmake 中可以使用条件判断。可以通过 cmake 官方文档 查看完整的表达式列表。
8.1 基本语法
cmake
if(expression)
# then section.
command1(args ...)
command2(args ...)
...
elseif(expression2)
# elseif section.
command1(args ...)
command2(args ...)
...
else()
# else section.
command1(args ...)
command2(args ...)
...
endif()
else 和 endif 括号中的表达式可写可不写,如果写了就必须和 if 中的表达式一致。
8.2 表达式详解
下表列出了常用表达式,下面逐一进行详解。
| 表达式 | true 条件 | false 条件 | 说明 | 示例 |
|---|---|---|---|---|
<constant> |
1、ON、YES、TRUE、Y、非零数 | 0、OFF、NO、FALSE、N、IGNORE、NOTFOUND、空字符串、以 -NOTFOUND 结尾 |
不区分大小写 | if(ON) → true |
| `<variable | string>` | 已定义且不是 false 的变量 | 未定义或为 false 的变量 | 变量就是字符串 |
NOT <expression> |
expression 为 false | expression 为 true | 取反 | if(NOT 0) → true |
<expr1> AND <expr2> |
两者同时为 true | 至少一个为 false | 逻辑与 | if(yes AND on) → true |
<expr1> OR <expr2> |
至少一个为 true | 两者都为 false | 逻辑或 | if(yes OR no) → true |
COMMAND name |
name 是已定义的命令/宏/函数 | name 未定义 | --- | if(COMMAND project) → true |
TARGET name |
name 是已定义的目标 | name 未定义 | --- | if(TARGET hello) → true(定义后) |
TEST name |
name 是已创建的测试名 | name 未创建 | --- | --- |
EXISTS path |
path 指定的文件或目录存在 | path 不存在 | 仅适用于完整路径(绝对路径) | if(EXISTS /tmp) → true |
IS_DIRECTORY path |
path 是目录 | path 不是目录 | 仅适用于完整路径 | if(IS_DIRECTORY /tmp) → true |
IS_SYMLINK path |
path 是符号链接 | path 不是符号链接 | 仅适用于完整路径 | --- |
IS_ABSOLUTE path |
path 是绝对路径 | path 不是绝对路径 | --- | if(IS_ABSOLUTE /tmp) → true |
| `<var | str> MATCHES regex` | 与正则表达式匹配成功 | 匹配失败 | --- |
| `<var | str> IN_LIST ` | 列表中包含该元素 | 列表中不含该元素 | --- |
DEFINED <variable> |
变量已定义 | 变量未定义 | 变量值真假不重要 | if(DEFINED VAR) → true(已 set) |
| `<var | str> LESS <var | str>` | 左侧数字小于右侧 | 左侧数字大于等于右侧 |
| `<var | str> GREATER <var | str>` | 左侧数字大于右侧 | 左侧数字小于等于右侧 |
| `<var | str> EQUAL <var | str>` | 左侧数字等于右侧 | 左侧数字不等于右侧 |
<constant>(常量)
在 if(constant) 条件判断中,如果 constant 是 1、ON、YES、TRUE、Y 或非零数字,则条件为 true;如果 constant 是 0、OFF、NO、FALSE、N、IGNORE、NOTFOUND、空字符串或以后缀 -NOTFOUND 结尾,则条件为 false。这些常量不区分大小写。如果参数不是这些特定常量之一,则将其视为变量或字符串,使用其他表达式规则。
cmake
if(ON)
message(true)
else()
message(false)
endif()
# 输出: true
if(YES)
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(NO)
message(true)
else()
message(false)
endif()
# 输出: false
<variable/string>(变量/字符串)
如果变量已经定义,并且它的值是一个非假常量,则条件为 true;否则为 false。注意宏参数不是变量。
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(因为 GG 的值是 NO,为假常量)
NOT <expression>(取反)
类似于 C 语言中的取反 !。如果表达式 expression 为真,则条件为假;如果表达式 expression 为假,则条件为真。
cmake
if(NOT 0)
message(true)
else()
message(false)
endif()
# 输出: true(0 为假,取反后为真)
if(NOT YES)
message(true)
else()
message(false)
endif()
# 输出: false(YES 为真,取反后为假)
<expr1> AND <expr2>(逻辑与)
类似于 C 语言中的 &&。只有 expr1 和 expr2 同时为真时,条件才为真;否则为假。
cmake
if(yes AND on)
message(true)
else()
message(false)
endif()
# 输出: true
if(yes AND no)
message(true)
else()
message(false)
endif()
# 输出: false
<expr1> OR <expr2>(逻辑或)
类似于 C 语言中的 ||。当 expr1 或 expr2 至少有一个为真时,条件为真;否则为假。
cmake
if(yes OR no)
message(true)
else()
message(false)
endif()
# 输出: true
if(false OR no)
message(true)
else()
message(false)
endif()
# 输出: false
COMMAND name
如果 name 是一个已经定义的命令、宏或函数时,条件为真;否则为假。
cmake
if(COMMAND project)
message(true)
else()
message(false)
endif()
# 输出: true(project 是 cmake 内置命令)
if(COMMAND yyds)
message(true)
else()
message(false)
endif()
# 输出: false(yyds 不是一个已定义的命令)
TARGET name
如果 name 是 add_executable()、add_library() 或 add_custom_target() 定义的目标,则条件为真;否则为假。目标在整个工程中必须是唯一的。
cmake
if(TARGET hello)
message(true)
else()
message(false)
endif()
# 输出: false(hello 目标尚未定义)
add_library(hello hello.c)
if(TARGET hello)
message(true)
else()
message(false)
endif()
# 输出: true(hello 目标已定义)
EXISTS path
如果 path 指定的文件或目录存在 ,则条件为真;否则为假。需要注意的是,path 必须是文件或目录的全路径(绝对路径)。
目录结构:
├── build
├── CMakeLists.txt
├── hello
│ ├── hello.c
│ └── hello.h
└── main.c
cmake
if(EXISTS ${PROJECT_BINARY_DIR})
message(true)
else()
message(false)
endif()
# 输出: true(build 目录存在)
if(EXISTS ${PROJECT_BINARY_DIR}/world)
message(true)
else()
message(false)
endif()
# 输出: false(build/world 不存在)
IS_DIRECTORY path
如果 path 指定的路径是一个目录,则条件为真;否则为假。path 也必须是一个绝对路径。
cmake
if(IS_DIRECTORY ${PROJECT_BINARY_DIR}/hello)
message(true)
else()
message(false)
endif()
# 输出: true(hello 是一个目录)
if(IS_DIRECTORY ${PROJECT_BINARY_DIR}/hello/hello.c)
message(true)
else()
message(false)
endif()
# 输出: false(hello.c 是一个文件,不是目录)
IS_ABSOLUTE path
如果给定的路径 path 是一个绝对路径,则条件为真;否则为假。
cmake
if(IS_ABSOLUTE ${PROJECT_BINARY_DIR})
message(true)
else()
message(false)
endif()
# 输出: true(PROJECT_BINARY_DIR 是绝对路径)
if(IS_ABSOLUTE ./hello)
message(true)
else()
message(false)
endif()
# 输出: false(./hello 是相对路径)
<variable|string> MATCHES regex
用于字符串匹配,可以使用正则表达式 进行匹配,也可以直接匹配普通字符串(普通字符串本身就是一个合法的正则表达式)。
cmake
set(MY_STR "Hello World")
# 匹配普通字符串
if(MY_STR MATCHES "Hello World")
message(true)
else()
message(false)
endif()
# 输出: true
# 使用正则表达式匹配
if("Hello123" MATCHES "^Hello[0-9]+$")
message(true)
else()
message(false)
endif()
# 输出: true
<variable|string> IN_LIST <variable>
如果左边给定的变量或字符串是右边列表中的某个元素,则条件为真;否则为假。
cmake
set(MY_LIST Hello World China)
if(Hello IN_LIST MY_LIST)
message(true)
else()
message(false)
endif()
# 输出: true
DEFINED <variable>
如果给定的变量已经定义 (即被 set 命令设置过),则条件为真;否则为假。只要变量已经被设置,if 条件就是真,至于变量的值是真是假并不重要。
cmake
if(DEFINED yyds)
message(true)
else()
message(false)
endif()
# 输出: false(yyds 变量未定义)
set(yyds "YYDS")
if(DEFINED yyds)
message(true)
else()
message(false)
endif()
# 输出: true(yyds 变量已定义)
<variable|string> LESS / GREATER / EQUAL
用于数字比较。如果左右两侧的值是有效数字,则进行数值比较。LESS 为小于 时为真,GREATER 为大于 时为真,EQUAL 为等于时为真。
cmake
if(100 GREATER 20)
message(true)
else()
message(false)
endif()
# 输出: true
if(20 GREATER 100)
message(true)
else()
message(false)
endif()
# 输出: false
if(100 EQUAL 100)
message(true)
else()
message(false)
endif()
# 输出: true
elseif 分支
可以使用 elseif 组成多个不同的分支:
cmake
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()
9. 循环语句
9.1 foreach 循环
基本用法 :遍历参数列表中的元素。loop_var 是一个循环变量,循环过程中会将参数列表中的值依次赋值给它。endforeach 括号中的变量可写可不写,如果写了就必须和 foreach 中的一致。
cmake
foreach(loop_var arg1 arg2 ...)
command1(args ...)
command2(args ...)
...
endforeach()
示例:
cmake
foreach(loop_var A B C D)
message("${loop_var}")
endforeach()
输出:
A
B
C
D
也可以遍历列表变量:
cmake
set(my_list hello world china)
foreach(loop_var ${my_list})
message("${loop_var}")
endforeach()
输出:
hello
world
china
RANGE 关键字:
有两种用法。第一种从 0 循环到指定的数字 stop(包含 stop),stop 不能为负数。第二种从 start 循环到 stop,步长为 step。step 可选,默认 step=1,三个参数都不能为负数,且 stop 不能比 start 小。
cmake
# 从 0 到 stop(包含 stop)
foreach(loop_var RANGE stop)
# 从 start 到 stop,步长为 step(step 可选,默认 1)
foreach(loop_var RANGE start stop [step])
示例:
cmake
foreach(loop_var RANGE 4)
message("${loop_var}")
endforeach()
输出:
0
1
2
3
4
cmake
foreach(loop_var RANGE 1 4 1)
message("${loop_var}")
endforeach()
输出:
1
2
3
4
IN 关键字:
可以循环列表中的每一个元素,也可以直接指定元素。
cmake
foreach(loop_var IN [LISTS [list1 [...]]] [ITEMS [item1 [...]]])
示例(遍历列表变量):
cmake
set(my_list A B C D)
foreach(loop_var IN LISTS my_list)
message("${loop_var}")
endforeach()
输出:
A
B
C
D
示例(直接指定元素):
cmake
foreach(loop_var IN ITEMS A B C D)
message("${loop_var}")
endforeach()
输出同上。
9.2 while 循环
while 循环的含义与 C 语言相同,当条件 condition 为真时执行循环体。condition 的语法形式与 if 条件判断相同。endwhile 括号中的表达式可写可不写,如果写了就必须和 while 中的一致。
cmake
while(condition)
command1(args ...)
command2(args ...)
...
endwhile()
示例(打印递减数字):
cmake
set(loop_var 4)
while(loop_var GREATER 0) # loop_var > 0 时执行循环体
message("${loop_var}")
math(EXPR loop_var "${loop_var} - 1") # loop_var--
endwhile()
输出:
4
3
2
1
9.3 break 和 continue
cmake 中也可以在循环体中使用 break() 和 continue() 语句,与 C 语言中的作用相同。
break 示例:
cmake
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()
输出:
10
9
8
7
6
5
break
continue 示例(打印 0 到 10 之间的偶数):
cmake
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()
输出:
10
8
6
4
2
10. 数学运算(math)
CMake 使用 math() 命令实现数学运算功能。math 命令中,第一个参数是固定的关键字 EXPR,第二个参数是返回变量(将数学运算结果存放在这个变量中),第三个参数是数学运算表达式。
cmake
math(EXPR <output variable> <math expression>)
支持的运算符包括:+(加)、-(减)、*(乘)、/(除)、%(求余)、|(按位或)、&(按位与)、^(按位异或)、~(按位取反)、<<(左移)、>>(右移)以及这些运算符的组合运算,它们的含义与 C 语言中相同。
示例:
cmake
cmake_minimum_required("VERSION" "3.5")
project("HELLO")
# 计算 1+1
math(EXPR out_var "1+1")
message("${out_var}")
# 计算 100x2
math(EXPR out_var "100 * 2")
message("${out_var}")
# 计算 100 + 100
math(EXPR out_var "100 + 100")
message("${out_var}")
# 计算 100 - 50
math(EXPR out_var "100 - 50")
message("${out_var}")
# 计算 100 * 100
math(EXPR out_var "100 * 100")
message("${out_var}")
# 计算 100 / 50
math(EXPR out_var "100 / 50")
message("${out_var}")
# 组合运算 (100 & 100) * 50 - 2
math(EXPR out_var "(100 & 100) * 50 - 2")
message("${out_var}")
执行 cmake 后打印信息如下:
2
200
200
50
10000
2
4998