DevTools2026年7月24日· 约 69 分钟

CMake入门——基本语法规则

#CMake#构建系统#语法规则#项目配置
Twitter 微博

CMake入门——基本语法规则

CMake 基本语法规则

1. 注释#

CMakeLists.txt 文件中,使用 # 号进行单行注释:

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

2. 命令(command)#

命令的使用方式类似于 C 语言中的函数,命令后面需要提供一对括号,并且通常需要提供参数,多个参数使用空格分隔而不是逗号 ,

语法格式:

command(参数1 参数2 参数3 ...)
  • 必要参数:使用 <参数> 表示
  • 可选参数(选项):使用 [参数] 表示

示例(set 命令):

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

命令名不区分大小写,可以使用大写或小写字母书写命令名:

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

两者效果相同。通常建议使用小写字母,以便与 CMake 内置变量(通常全大写)区分开来。

3. 变量(variable)#

使用 set 命令设置变量:

set(MY_VAL "Hello World!")

引用变量使用 ${变量名} 方式:

set(MY_VAL "Hello World!")
message(${MY_VAL})

变量分为 CMake 内置变量(如 LIBRARY_OUTPUT_PATHEXECUTABLE_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 命令用于添加一个可执行程序目标,并设置目标所需的源文件,命令定义如下:

add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] source1 [source2 ...])

该命令提供了一些可选参数(WIN32MACOSX_BUNDLEEXCLUDE_FROM_ALL),通常不需要加入,其具体含义可以查看 cmake 官方文档。只需传入目标名和对应的源文件即可。

示例:

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

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

4.2 add_library#

添加一个库文件目标,并设置目标所需的源文件:

add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
  • 默认生成静态库文件
  • 通过 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_executableadd_library 所定义的目标名在整个工程中必须是唯一的,不可出现同名目标。

4.3 add_subdirectory#

add_subdirectory 命令告诉 cmake 去指定的目录中寻找源码(CMakeLists.txt)并执行它,类似于 Makefile 的 include,命令定义如下:

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_minimum_required("VERSION" "3.5")
project("HELLO")

告诉 cmake 去 src 目录下寻找 CMakeLists.txt

add_subdirectory(src)

src/CMakeLists.txt

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_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:

add_subdirectory(../lib)   # 错误!会报错

必须显式指定:

add_subdirectory(../lib output)

4.4 aux_source_directory#

aux_source_directory 命令会查找指定目录中的所有源文件,并将扫描到的源文件路径信息存放到变量中,命令定义如下:

aux_source_directory(<dir> <variable>)

示例(目录结构如下):

├── build
├── CMakeLists.txt
└── src
    ├── 1.c
    ├── 2.c
    ├── 2.cpp
    └── main.c
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 选项,命令定义如下:

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

默认情况下会将指定目录添加到头文件搜索列表(可以认为每一个 CMakeLists.txt 源码都有自己的头文件搜索列表)的最后面。可以通过以下方式改变行为:

  • 设置 CMAKE_INCLUDE_DIRECTORIES_BEFORE 变量为 ON,使默认添加位置改为列表前面
  • 使用 AFTERBEFORE 选项显式指定添加到列表后面或前面
  • 使用 SYSTEM 选项将指定目录视为系统的搜索目录

支持绝对路径和相对路径,相对路径被解释为当前源码路径的相���路径。

示例(目录结构如下):

├── build
├── CMakeLists.txt
├── include
│   └── hello.h
└── main.c
cmake_minimum_required("VERSION" "3.5")
project("HELLO")

include_directories(include) add_executable(hello main.c)

如果不加 include_directories(include) 这条命令,编译时会报错提示找不到头文件。

使用 AFTERBEFORE 选项:

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

添加到列表前面

include_directories(BEFORE include)

继承性:当调用 add_subdirectory 加载子源码时,会将 include_directories 包含的目录列表向下传递给子源码(子源码从父源码中继承过来)。

  • 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
cmake_minimum_required("VERSION" "3.5")
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(${PROJECT_SOURCE_DIR}/lib/libhello.so)

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

4.8 list#

list 命令是一个关于列表操作的命令,列表其实就是字符串数组(也叫字符串列表)。命令定义如下:

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_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 命令:

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

mode 关键字说明:

mode 说明
重要信息、普通信息
STATUS 附带信息
WARNING CMake 警告,继续处理
AUTHOR_WARNING CMake 警告(开发),继续处理
SEND_ERROR CMake 错误,继续处理,但跳过生成
FATAL_ERROR CMake 错误,停止处理和生成
DEPRECATION CMake 弃用错误或警告(取决于变量设置)

示例:

message("Hello World!")

message(STATUS "This is a status message")

message(FATAL_ERROR "An error occurred, stop processing!")

4.10 project#

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

project(HELLO)

执行 project(HELLO) 后会引入两个变量:HELLO_SOURCE_DIRHELLO_BINARY_DIR。注意这两个变量名的前缀就是工程名称,其中 HELLO_SOURCE_DIR 变量指的是 HELLO 工程源码目录(顶层 CMakeLists.txt 所在目录),HELLO_BINARY_DIR 变量指的是 HELLO 工程源码的输出文件目录。如果不加入 project(HELLO) 命令,这两个变量是不存在的。

cmake 还定义了两个等价的变量 PROJECT_SOURCE_DIRPROJECT_BINARY_DIR。在单工程(只有一个 project())的情况下,HELLO_SOURCE_DIRPROJECT_SOURCE_DIR 指向同一目录。但如果子目录中又调用了另一个 project(),则 PROJECT_SOURCE_DIR 会变为最近一次 project() 所在目录,而 HELLO_SOURCE_DIR 始终指向 HELLO 工程的顶层目录,不会改变。因此,推荐使用 PROJECT_SOURCE_DIRPROJECT_BINARY_DIR,这样不依赖具体工程名,写出的 CMakeLists.txt 更通用。

也可以同时设置版本号:

project(HELLO VERSION 1.1.0)

示例(打印工程信息):

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#

设置变量的值:

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

可选参数 PARENT_SCOPE 影响变量的作用域。

示例:

set(VAR1 Hello)
set(VAR2 World)

message(${VAR1} " " ${VAR2})

执行 cmake 后打印信息如下:

Hello World

target_include_directories 命令为指定目标设置头文件搜索路径,而 target_link_libraries 命令为指定目标设置链接库文件。它们与 include_directories / 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_executableadd_library 等命令所创建的目标。SYSTEMBEFORE 选项与 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):

# 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)
# 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)
# 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_directoriestarget_link_libraries

include_directories()link_libraries() 是针对当前源码中的所有目标,并且会向下传递(例如通过 add_subdirectory 加载子源码时,也会将其传递给子源码)。在一个大的工程当中,这通常不规范,有时还会导致编译错误和混乱。所以应尽量使用 target_include_directories()target_link_libraries(),保持整个工程的目录清晰。


5. 字符串列表#

通过 set 命令可以创建字符串列表(也叫字符串数组):

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

此时 SRC_LIST 就是一个包含 5 个元素的列表(1.c2.c3.c4.c5.c),列表的各个元素使用分号 ; 分隔:

SRC_LIST = 1.c;2.c;3.c;4.c;5.c

引用列表时,双引号的作用至关重要

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} 放在双引号中再次测试:

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_DIRPROJECT_BINARY_DIR

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

示例:

目录结构:

├── build
├── CMakeLists.txt
└── main.c
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_DIRCMAKE_BINARY_DIR

与上面两个等价。

CMAKE_CURRENT_SOURCE_DIRCMAKE_CURRENT_BINARY_DIR

CMAKE_CURRENT_SOURCE_DIR 指的是当前正在被 cmake 处理的 CMakeLists.txt 所在的路径CMAKE_CURRENT_BINARY_DIR 则是这个 CMakeLists.txt 对应的输出目录。当通过 add_subdirectory 加载子源码时,这两个变量的值会随之变化(指向子源码对应的路径),而 PROJECT_SOURCE_DIRPROJECT_BINARY_DIR 始终指向顶层目录,不会改变。

示例:

目录结构:

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

顶层 CMakeLists.txt

cmake_minimum_required("VERSION" "3.5")
project("HELLO")
add_subdirectory(src)

src/CMakeLists.txt

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_DIRPROJECT_BINARY_DIR 始终指向顶层目录(./build/),不会因为进入了子源码而改变。

CMAKE_VERSIONCMAKE_MAJOR_VERSIONCMAKE_MINOR_VERSION

记录 cmake 的版本号:

message(${CMAKE_VERSION})
message(${CMAKE_MAJOR_VERSION})
message(${CMAKE_MINOR_VERSION})

打印信息示例:

3.5.1
3
5

PROJECT_VERSIONPROJECT_VERSION_MAJORPROJECT_VERSION_MINOR

记录工程的版本号,通过 project() 命令设置版本号:

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_NAMEPROJECT_NAME

这两个是等价的,记录了工程的名字:

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 指定构建类型,DebugRelease
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_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.solibworld.so(而非默认的静态库 libhello.alibworld.a)。

CMAKE_BUILD_TYPE

设置编译类型 DebugRelease。Debug 版会生成相关调试信息,可以使用 GDB 进行调试;Release 不会生成调试信息。

# 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_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_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() 会将目录添加到列表前面。

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_NAMECMAKE_HOST_SYSTEM_PROCESSORCMAKE_HOST_SYSTEMCMAKE_HOST_SYSTEM_VERSION

这四个变量描述的是运行 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_NAMECMAKE_SYSTEM_PROCESSORCMAKE_SYSTEMCMAKE_SYSTEM_VERSION

这四个变量描述的是目标主机相关的信息。目标主机指的是可执行文件将要运行的主机(例如 ARM 开发板)。如果没有配置交叉编译,默认使用主机本身的编译工具,生成的目标文件只能运行在当前主机上,所以它们的值与 CMAKE_HOST_* 相同。

message(${CMAKE_SYSTEM_NAME})
message(${CMAKE_SYSTEM_PROCESSOR})
message(${CMAKE_SYSTEM})
message(${CMAKE_SYSTEM_VERSION})

在未配置交叉编译的情况下,打印信息与 CMAKE_HOST_* 一致。

ENV

用于访问系统的环境变量,用法为 $ENV{VAR}

# 访问环境变量
message($ENV{XXX})

首先在终端中使用 export 命令导出 XXX 环境变量,然后执行 cmake:

export XXX="Hello World!"
cd build/
cmake ..

打印信息如下:

Hello World!

从打印信息可知,ENV 变量确实可以访问到系统的环境变量。

CMAKE_HOST_UNIXCMAKE_HOST_WIN32(以及等价的 UNIXWIN32

CMAKE_HOST_UNIX 在运行 cmake 的操作系统是 UNIX 或类 UNIX 系统时为 true,否则为空值;CMAKE_HOST_WIN32 在 Windows 系统上为 true,否则为空值。

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.somain.c 会被编译成可执行程序,main.c 中调用了 hello.c 提供的函数:

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_PATHLIBRARY_OUTPUT_PATH,那么生成的目标文件会默认与中间文件一起放在 build/ 目录中。


7. 双引号的作用#

CMake 中,双引号的作用可以从两个方面来理解:命令参数引用变量

7.1 命令参数#

调用命令时,参数可以使用双引号,也可以不使用。双引号会将引起来的内容作为一个整体参数。如果参数中包含空格(空格是参数的一部分),那么就必须使用双引号。

message(Hello World)       # 传入两个参数:Hello 和 World
message("Hello World")     # 只传入一个参数:Hello World

第一个 message 命令传入了两个参数(HelloWorld),打印时会将两个字符串拼接在一起输出,World 紧跟在 Hello 之后:

HelloWorld

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

Hello World

这就是双引号在参数中的作用。

7.2 引用变量#

先来看一个例子:

set(MY_LIST Hello World China)
message(${MY_LIST})

打印信息如下:

HelloWorldChina

在这个例子中,MY_LIST 是一个列表,包含了 3 个元素(HelloWorldChina)。但 message 命令打印时将这 3 个元素全部打印出来,并且各个元素之间没有任何分隔。

此时在引用变量(${MY_LIST})时加上双引号:

message("${MY_LIST}")

打印信息如下:

Hello;World;China

因为此时 ${MY_LIST} 是一个列表,用 "${MY_LIST}" 这种形式时,表示要让 cmake 把这个列表的所有元素当成一个整体,而不是分散的个体。于是为了保持数组的含义,又提供一个整体的表达方式,cmake 会用分号 ; 把列表的多个元素连接起来。

而如果不加双引号时,cmake 不会把列表当成一个整体看待,而是会将列表中的各个元素提取出来直接拼接打印输出。

引用列表时,使用 "${变量名}" 形式可以保持列表的分隔符(分号),而不加双引号时会将各元素直接拼接输出。


8. 条件判断#

在 cmake 中可以使用条件判断。可以通过 cmake 官方文档 查看完整的表达式列表。

8.1 基本语法#

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

elseendif 括号中的表达式可写可不写,如果写了就必须和 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 的变量 变量就是字符串 if(GG) → true(set(GG Hello)
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 与正则表达式匹配成功 匹配失败 if("Hi" MATCHES "Hi") → true
<var|str> IN_LIST <var> 列表中包含该元素 列表中不含该元素 if(Hello IN_LIST MY_LIST) → true
DEFINED <variable> 变量已定义 变量未定义 变量值真假不重要 if(DEFINED VAR) → true(已 set)
<var|str> LESS <var|str> 左侧数字小于右侧 左侧数字大于等于右侧 if(5 LESS 10) → true
<var|str> GREATER <var|str> 左侧数字大于右侧 左侧数字小于等于右侧 if(10 GREATER 5) → true
<var|str> EQUAL <var|str> 左侧数字等于右侧 左侧数字不等于右侧 if(5 EQUAL 5) → true

<constant>(常量)

if(constant) 条件判断中,如果 constant 是 1ONYESTRUEY 或非零数字,则条件为 true;如果 constant 是 0OFFNOFALSENIGNORENOTFOUND、空字符串或以后缀 -NOTFOUND 结尾,则条件为 false。这些常量不区分大小写。如果参数不是这些特定常量之一,则将其视为变量或字符串,使用其他表达式规则。

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。注意宏参数不是变量。

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 为假,则条件为真。

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 同时为真时,条件才为真;否则为假。

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 至少有一个为真时,条件为真;否则为假。

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 是一个已经定义的命令、宏或函数时,条件为真;否则为假。

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() 定义的目标,则条件为真;否则为假。目标在整个工程中必须是唯一的。

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
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 也必须是一个绝对路径。

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 是一个绝对路径,则条件为真;否则为假。

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

用于字符串匹配,可以使用正则表达式进行匹配,也可以直接匹配普通字符串(普通字符串本身就是一个合法的正则表达式)。

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>

如果左边给定的变量或字符串是右边列表中的某个元素,则条件为真;否则为假。

set(MY_LIST Hello World China)

if(Hello IN_LIST MY_LIST) message(true) else() message(false) endif()

输出: true

DEFINED <variable>

如果给定的变量已经定义(即被 set 命令设置过),则条件为真;否则为假。只要变量已经被设置,if 条件就是真,至于变量的值是真是假并不重要。

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等于时为真。

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 组成多个不同的分支:

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 中的一致。

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

示例:

foreach(loop_var A B C D)
    message("${loop_var}")
endforeach()

输出:

A
B
C
D

也可以遍历列表变量:

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,步长为 stepstep 可选,默认 step=1,三个参数都不能为负数,且 stop 不能比 start 小。

# 从 0 到 stop(包含 stop)
foreach(loop_var RANGE stop)

从 start 到 stop,步长为 step(step 可选,默认 1)

foreach(loop_var RANGE start stop [step])

示例:

foreach(loop_var RANGE 4)
    message("${loop_var}")
endforeach()

输出:

0
1
2
3
4
foreach(loop_var RANGE 1 4 1)
    message("${loop_var}")
endforeach()

输出:

1
2
3
4

IN 关键字

可以循环列表中的每一个元素,也可以直接指定元素。

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

示例(遍历列表变量):

set(my_list A B C D)
foreach(loop_var IN LISTS my_list)
    message("${loop_var}")
endforeach()

输出:

A
B
C
D

示例(直接指定元素):

foreach(loop_var IN ITEMS A B C D)
    message("${loop_var}")
endforeach()

输出同上。

9.2 while 循环#

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

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

示例(打印递减数字):

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 示例

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 之间的偶数):

set(loop_var 10)

while(loop_var GREATER 0) # loop_var > 0 时执行循环体 math(EXPR var "${loop_var} % 2") # 求余

if(var EQUAL class="hljs-number">0)         class=class="hljs-string">"hljs-comment"># 如果 var == class="hljs-number">0,表示它是偶数
    message(class="hljs-string">"${loop_var}")         class=class="hljs-string">"hljs-comment"># 打印这个偶数
    math(EXPR loop_var class="hljs-string">"${loop_var} - class="hljs-number">1")   class=class="hljs-string">"hljs-comment"># loop_var--
    continue()          class=class="hljs-string">"hljs-comment"># 执行下一次循环
endif()

math(EXPR loop_var class="hljs-string">"${loop_var} - class="hljs-number">1")   class=class="hljs-string">"hljs-comment"># loop_var--

endwhile()

输出:

10
8
6
4
2

10. 数学运算(math)#

CMake 使用 math() 命令实现数学运算功能。math 命令中,第一个参数是固定的关键字 EXPR,第二个参数是返回变量(将数学运算结果存放在这个变量中),第三个参数是数学运算表达式。

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

支持的运算符包括:+(加)、-(减)、*(乘)、/(除)、%(求余)、|(按位或)、&(按位与)、^(按位异或)、~(按位取反)、<<(左移)、>>(右移)以及这些运算符的组合运算,它们的含义与 C 语言中相同。

示例:

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

原文链接:https://www.cnblogs.com/Javenwww/p/21872999

评论

© 2026 松岛川树