CMakeLists.txt 基本指令 - YHT's Note Repo%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
%%
# 搜索路径引入
- `include_directories()`: 引入**头文件查找路径**
- `link_directorires()`:引入**库文件查找路径**
上述命令均有 `target_xxx()` 版本,用于为 **特定构建目标** 引入额外的搜索路径。
```cmake title=CMakeLists.txt
# 引入额外的头文件搜索路径(作用于全局)
include_directories(${PROJECT_SOURCE_DIR}/include)
# 引入额外的库文件搜索路径(作用于全局)
link_directories(${PROJECT_SOURCE_DIR}/lib)
```
<br><br>
# 名称指示关键字
`TARGET`、`TARGETS`、`DESTINATION`、`FILES`、`NAMES`、`COMMAND` 等关键字用于 **指示其后所跟字符串的含义**。
### `TARGET` 与 `TARGETS` 关键字
`TARGET` 和 `TARGETS` 关键字用于指定构建过程中涉及的构建目标,这些**关键字之后会跟随有目标名**,以指定对该目标应用操作或属性。
- `TARGET` 用于**指定单个构建目标**
- `TARGETS` 用于**指定多个构建目标**,常用于安装(`install`)、导出(`export`)等操作中
示例:
```cmake
# 示例一:
add_executable(TARGET my_executable main.cpp) # 这里的TARGET关键字可省略
# 示例二:
install(TARGETS my_library my_executable DESTINATION lib)
# 示例三:
set(installable_libs MathFunctions tutorial_compiler_flags)
if(TARGET SqrtLibrary) # 当SqrtLibrary目标存在时, 添加到installable_libs中.
list(APPEND installable_libs SqrtLibrary)
endif()
install(TARGETS ${installable_libs} DESTINATION lib)
```
<br><br><br>
# message 消息打印命令
CMake 中可通过 `message()` 命令 **打印消息** ,用于**调试、状态输出、错误提示**等,允许指定不同的**消息级别**,以控制输出行为。
> [!NOTE] `message()` 命令在 **==配置阶段==** 被执行,直接输出信息到终端。
```cmake
# 命令说明:
message([<模式>] "消息内容")
# 命令示例:
message(STATUS "CMake is configuring the project...")
message(WARNING "This is a warning message!")
message(FATAL_ERROR "Configuration failed! Missing dependencies.")
```
`message()` 提供了以下**消息级别**:
| 消息级别 | 作用 |
| ----------------- | ------------------------ |
| **`STATUS`** | 标准状态信息 |
| **`WARNING`** | 警告信息 |
| **`DEPRECATION`** | 过时警告,用于标记已弃用的特性 |
| **`SEND_ERROR`** | 错误信息(不中断 CMake 运行) |
| **`FATAL_ERROR`** | 致命错误信息(会**终止 CMake 运行**) |
<br><br><br>
# find 系列命令
CMake 的 `find_xxx()` 系列命令主要用于在系统中**查找特定类型的文件或资源**
| 命令 | 作用 |
| ---------------- | ---------------------------------- |
| `find_file()` | 在指定路径中查找某个特定文件,取其**完整路径** |
| `find_path()` | 在指定路径中查找某个特定文件,取其**所在目录路径** |
| `find_library()` | 在指定路径中查找某个**库文件**,取其**完整路径** |
| `find_program()` | 在指定路径中查找某个**可执行文件**,取其**完整路径** |
| `find_package()` | 用于查找和配置**完整的软件包**(如 Boost、OpenCV)。 |
## `find_file()` 命令
说明:**在指定路径 `path1, path2, ...` 中查找指定文件 `name1, name2`,若文件存在,则将其==完整路径==存储于变量 `<VAR>` 中**。
```cmake
# 命令说明:
find_file(<VAR> name1 [name2 ...]
[PATHS path1 [path2 ...]]
[HINTS path1 [path2 ...]]
[REQUIRED] [NO_DEFAULT_PATH])
# 示例:若myheader.h存在于`/usr/include` 或 `/usr/local/include` 中, 则变量MY_INCLUDE_DIR存储该文件完整路径.
find_path(MY_INCLUDE_DIR myheader.h /usr/include /usr/local/include)
message(STATUS "Found include directory: ${MY_INCLUDE_DIR}")
```
## `find_path()` 命令
说明:**在指定路径 `path1, path2, ...` 中查找指定文件 `name1, name2`,若文件存在,则将其 ==所在目录== 存储于变量 `<VAR>` 中**。
```cmake
# 命令说明:
find_path(<VAR> name1 [name2 ...]
[PATHS path1 [path2 ...]]
[HINTS path1 [path2 ...]]
[REQUIRED] [NO_DEFAULT_PATH])
# 示例: 若myheader.h存在于`/usr/include` 或 `/usr/local/include` 中, 则变量MY_INCLUDE_DIR存储该目录路径.
find_path(MY_INCLUDE_DIR myheader.h /usr/include /usr/local/include)
message(STATUS "Found include directory: ${MY_INCLUDE_DIR}")
```
## `find_library()` 命令
说明:**在指定路径 `path1, path2, ...` 中查找指定==库文件== `name1, name2`(不带 `.lib` 或 `.o` 后缀),若文件存在,则将其 ==完整路径== 存储于变量 `<VAR>` 中**。
```cmake
# 命令说明:
find_library(<VAR> name1 [path1 path2 ...]
[DOC "cache description"]
[NO_DEFAULT_PATH] ...)
# 示例: 若mylib存在于`/usr/lib`中, 则赋予变量`MYLIB_LIBRARY=/usr/lib/libmylib.so`
find_library(MYLIB_LIBRARY mylib /usr/lib /usr/local/lib)
message(STATUS "Library found: ${MYLIB_LIBRARY}")
```
## `find_program()` 命令
说明:**在指定路径 `path1, path2, ...` 中查找指定可执行文件 `name1, name2`,若文件存在,则将其 ==完整路径== 存储于变量 `<VAR>` 中**。
```cmake
# 命令说明:
find_program(<VAR> name1 [path1 path2 ...]
[DOC "cache description"]
[NO_DEFAULT_PATH] ...)
# 示例: 若gcc存在于/usr/bin/gcc中, 则赋予变量GCC_EXECUTABLE=/usr/bin/gcc
find_program(GCC_EXECUTABLE gcc /usr/bin /usr/local/bin)
message(STATUS "GCC found: ${GCC_EXECUTABLE}")
```
## `find_package()` 命令
该命令用于在 **==配置阶段==** 自动**查找和定位外部库或软件包**,包括其头文件、库文件等。
一旦找到指定的包,该命令将 **自动设置** 相关的**CMake 变量**,如包含目录、库文件路径、其它必要的编译定义等。
随后,这些变量可以在`CMakeLists.txt`中被使用。
> [!NOTE] 自动设置的包相关变量包括:
>
> - `<PackageName>_FOUND`:标记包是否存在
> - `<PackageName>_INCLUDE_DIR` 或 `<PackageName>_INCLUDES`:包的头文件路径
> - `<PackageName>_LIBRARY` 或 `<PackageName>_LIBRARIES`:包的库文件路径
>
> [!caution] 注:确保在调用 `find_package()` 之前已经引入了必要的搜索路径(如 `CMAKE_MODULE_PATH`)
### 命令说明
```cmake
# Basic Signature
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[REGISTRY_VIEW (64|32|64_32|32_64|HOST|TARGET|BOTH)]
[GLOBAL]
[NO_POLICY_SCOPE]
[BYPASS_PROVIDER])
# Full Signature
find_package(<PackageName> [version] [EXACT] [QUIET]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[CONFIG|NO_MODULE]
[GLOBAL]
[NO_POLICY_SCOPE]
[BYPASS_PROVIDER]
[NAMES name1 [name2 ...]]
[CONFIGS config1 [config2 ...]]
[HINTS path1 [path2 ... ]]
[PATHS path1 [path2 ... ]]
[REGISTRY_VIEW (64|32|64_32|32_64|HOST|TARGET|BOTH)]
[PATH_SUFFIXES suffix1 [suffix2 ...]]
[NO_DEFAULT_PATH]
[NO_PACKAGE_ROOT_PATH]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[NO_SYSTEM_ENVIRONMENT_PATH]
[NO_CMAKE_PACKAGE_REGISTRY]
[NO_CMAKE_BUILDS_PATH] # Deprecated; does nothing.
[NO_CMAKE_SYSTEM_PATH]
[NO_CMAKE_INSTALL_PREFIX]
[NO_CMAKE_SYSTEM_PACKAGE_REGISTRY]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH])
```
主要参数:
- `REQUIRED` : 指定后,若**未找到包则报错**,停止配置过程。
- `QUIET`: 静默模式,未找到包时**不显示任何消息**。
- `EXACT`:要求查找精确版本
- `MODULE` 与 `NO_MODULE`: 指定搜索模式。前者表示只使用**模块模式**,后者表示只使用**配置模式**。
- `NAMES`:指定查找包的名称或别名。
- `PATHS`:提供一个或多个**库文件**查找路径。
- `NO_DEFAULT_PATH`:指示 CMake **不使用默认的路径** 进行搜索。
- 使用该选项时,CMake **只会在 `PATHS` 或 `HINTS` 中指定的路径里搜索库文件**,而不会去搜索任何预设或标准的路径。
- 默认路径通常包括:标准安装路径和 CMake 预定义的路径
> [!NOTE] 关于`NAMES`
> >
> 通常用于指定包的多个别名
>
> ```cmake
> find_package(SomeThing
> NAMES
> SameThingOtherName # Another name for the package
> SomeThing # Also still look for its canonical name
> )
> ```
>
> 该选项也用在`find_package()` 命令**首项参数为一个变量名的情况**,例如:
>
> ```cmake
> # 尝试查找定位库文件, 并将其路径存储在一个变量`GTEST_LIBRARY`中
> find_library(GTEST_LIBRARY NAMES gtest PATHS "path/to/gtest/lib" NO_DEFAULT_PATH)
> # 使用变量`GTEST_LIBRARY`:
> target_link_libraries(my_target ${GTEST_LIBRARY})
> ```
> > 注:CMake 查找时会根据平台自动在库前面添加适当前缀或后缀,例如`Unix-like`系统上,指定`NAMES gtest`时,CMake 会自动查找`libgtest.a` 或 `libgtest.so
>
### 使用示例
示例一:
```cmake
# 尝试找到至少版本为 1.65 的 Boost 库,并且需要包括 filesystem 和 system 组件.
# 如果未找到,CMake 配置将失败.
find_package(Boost 1.65 REQUIRED COMPONENTS filesystem system)
# 查找GTest库
find_package(GTest REQUIRED)
```
示例二:
```cmake
cmake_minimum_required(VERSION 2.8)
project(helloworld)
add_executable(helloworld hello.c)
# 尝试查找Bzip2库
find_package (BZip2)
# 如果找到, 则CMake会自动定义`<Package>_FOUND`, ``
if (BZIP2_FOUND)
include_directories(${BZIP_INCLUDE_DIRS})
target_link_libraries (helloworld ${BZIP2_LIBRARIES})
else(BZIP2_FOUND)
message(FATAL_ERROR ”BZIP2 library not found”)
endif (BZIP2_FOUND)
```
### 两种查找模式
`find_package()` 支持两种查找模式——模块模式和配置模式
- **模块模式**:使用预写的 `Find<Package>.cmake` 模块
- **配置模式**:使用由软件包本身提供的配置文件。
根据所使用的参数,`find_package()` 可以使用上述方法中的一个或两个。
> [!NOTE] 对于两种搜索模式,可以通过设置 `CMAKE_PREFIX_PATH`与 `CMAKE_MODULE_PATH` 两个变量**指定额外查找路径**。
>
> > [!quote]
> > If the location of the package is in a [directory known to CMake](https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure), the [`find_package()`](https://cmake.org/cmake/help/latest/command/find_package.html#command:find_package) call should succeed. The directories known to CMake are platform-specific. For example, **packages installed on Linux with a standard system package manager will be found in the `/usr` prefix automatically**. Packages installed in `Program Files` on Windows will similarly be found automatically.
> >
> > Packages will not be found automatically without help if they are in locations not known to CMake, such as `/opt/mylib` or `$HOME/dev/prefix`. This is a normal situation, and **CMake provides several ways for users to specify where to find such libraries**.
> >
> > The [`CMAKE_PREFIX_PATH`](https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html#variable:CMAKE_PREFIX_PATH) variable may be [set when invoking CMake](https://cmake.org/cmake/help/latest/guide/user-interaction/index.html#setting-build-variables). It is treated as a list of base paths in which to search for [config files](https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#config-file-packages).
>
>
#### 模块模式(Module mode)
模块模式下,CMake 将搜索名为 **`Find<PackageName>.cmake` 的 "==查找模块==" 文件**,该模块包含了**查找和设置指定包所需的逻辑**,用于**帮助 CMake 在系统中找到并设置外部库(如库文件和头文件)**。当成功找到指定的软件包时,该模块会定义/设置一系列 CMake 变量,以便后续在 CMake 中使用。
---
模块模式下,CMake 的查找目录包括(按顺序):
- (1)**变量 `CMAKE_MODULE_PATH` 指定的目录**。
- (2)CMake 安装目录(**`CMAKE_ROOT` 变量**)下的 `Modules`目录。
#### 配置模式(Config mode)
> 如果模块模式搜索失败,未找到对应的 **`Find<Package>.cmake` 模块文件**,则转入 Config 模式。
配置模式下,CMake 将搜索名称为下列形式的**配置文件**:
- `<lowercasePackageName>-config.cmake` 文件
- or `<PackageName>Config.cmake` 文件
当`find_package()` 命令提供了具体版本号时,则同时会搜索:
- `<lowercasePackageName>-config-version.cmake` 文件
- 或 `<PackageName>ConfigVersion.cmake` 文件
**这些配置文件通常作为包的一部分,由包本身/库开发者提供**,包含了导入库所需的所有信息。
---
配置模式下,CMake 的查找路径包括(按顺序):
- (1)**名为 `<PackageName>_DIR`的 CMake 变量或环境变量路径**
- 该变量对应的路径必须指定为配置文件`<PackageName>Config.cmake`或`<lower-case-package-name>-config.cmake` 所在的目录
- (2)**名为 `CMAKE_PREFIX_PATH`、`CMAKE_FRAMEWORK_PATH`、`CMAKE_APPBUNDLE_PATH`的 CMake 变量或环境变量路径**。
- 这一路径指定的是"**==路径前缀==**",即第三方包或库的**根目录**。
- (3)**`PATH` 系统环境变量路径**
- 这一路径指定的是第三方包或库的**根目录**。
> [!NOTE] 大部分情况下,Config 模式能够查找到安装到系统中各种库的都是通过 `PATH` 路径
注:在上述第二种、第三种情况下,CMake 会首先检查这些根目录路径下是否有名为`<PackageName>Config.cmake`或`<lower-case-package-name>-config.cmake`的模块文件,如果没有,CMake 会继续检查或匹配这些根目录下的以下路径(`<PackageName>_DIR`路径不是根目录路径)
```cmake
<prefix>/(lib/<arch>|lib|share)/cmake/<name>*/
<prefix>/(lib/<arch>|lib|share)/<name>*/
<prefix>/(lib/<arch>|lib|share)/<name>*/(cmake|CMake)/
```
## 搜索路径
![[05-工具/CMake 构建工具/CMakeLists.txt 变量与缓存#CMake 搜索路径|CMakeLists.txt 变量与缓存]]
### 搜索路径设置建议
- 对于**特定包**——可**单独为其指定搜索路径**:添加一个 `<PackageName>_DIR` 变量,值为该包的**配置文件**所在**目录**。
- (配置文件:`<PackageName>Config.cmake` 或 `<lowercasePackageName>-config.cmake` )
- 对于**多个包**:可**将多个包的配置文件都统一放在一个命名为`cmake`的文件夹下,而后向 `CMAKE_PREFIX_PATH` 变量追加该路径**
- 注意:根据匹配规则,每个包的配置文件 **需单独放置在==命名为包名==的文件夹下**(文件夹名不区分大小写),否则会提示找不到。
> [!example] 将 GTest 包 `.cmake` 文件所在路径设置为 `GTest_DIR` 变量,或者追加到 `CMAKE_PREFIX_PATH` 变量中
>
> 
>
<br><br>
## 查找模块
名为 `Find<PackageName>.cmake` 的文件称之为 "**==查找模块==**(本质是一个 **CMake 脚本**),该模块包含了 **查找和设置包相关的 CMake 变量所需的逻辑**,用于**帮助 CMake 在系统中找到并设置外部库(如库文件和头文件)、程序和其他文件**,由 `find_package()` 命令使用。
当 Find Module 找到指定的软件包或资源时,将会**设置一系列 CMake 变量**,这些变量包括路径信息、找到的库的名称等,以便后续在 CMake 中使用。
> [!faq] ❓ “查找模块” 是由谁来提供的?
>
> - “查找模块” 通常**并不由包或依赖库本身提供**,而是由外部提供,例如**CMake 工具本身**、**第三方 C++包管理工具**(Conan 或 Vcpkg[^3])。
> - **CMake 内置了一系列预定义的 `Find<PackageName>.cmake` 模块,用于搜索相应的第三方库**,例如 `FindGTest.cmake` 、`FindCURL.cmake` 等
> - 此外,项目开发者也可以自己编写 `Find<PackageName>.cmake`。
>
> [!example] CMake 内置的 "查找模块"
> 
> [!example] CMake 内置的`FindGTest.cmake` 模块[^4]
>
> 该通过 `find_package()` 命令查找到该模块后将进行执行()将设置如下变量:
>
> ```cmake
> # IMPORTED targets
> GTest:: gtest # 为向后兼容, 还有一个等效的 GTest::GTest
> GTest:: gtest_main # 为向后兼容, 还有一个等效的 GTest::Main
> GTest::gmock
> GTest::gmock_main
>
> # Result variables
> GTest_FOUND # 如果找到, 则该变量为 1
> GTEST_INCLUDE_DIRS # Google Test 头文件的路径
> GTEST_LIBRARIES # Google Test 的`gtest`库
> GTEST_MAIN_LIBRARIES # Google Test 的`gtest_main`库
> GTEST_BOTH_LIBRARIES # Both `gtest` and `gtest_main`
>
> # Cache variables
> GTEST_ROOT # The root directory of the Google Test installation
> GTEST_MSVC_SEARCH # If compiling with MSVC, this variable can be set to MT or MD (the default) to enable searching a GTest build tree
> ```
>
> 
>
<br><br><br>
# install 命令
`CMakeLists.txt` 中通过 `install()` 命令指定安装规则:
```cmake
# 指定安装规则
install(TARGETS <target>... [...])
install(IMPORTED_RUNTIME_ARTIFACTS <target>... [...])
install({FILES | PROGRAMS} <file>... [...])
install(DIRECTORY <dir>... [...])
install(SCRIPT <file> [...])
install(CODE <code> [...])
install(EXPORT <export-name> [...])
install(RUNTIME_DEPENDENCY_SET <set-name> [...])
# 语法
# `TARGETS <target>...` 指定要安装的目标(们)
# `DESTINATION <dir>` 指定要将文件安装到的磁盘目录。参数可以是相对路径或绝对路径。
# 为相对路径时, 是相对于安装前缀(由`CMAKE_INSTALL_PREFIX`变量定义)
install(TARGETS my_library my_executable DESTINATION <dir>)
install(FILES MathFunctions.h DESTINATION include) # 可以是文件, 由"FILES"关键字指定
```
- `TARGEST <target>`
- `DESTINATION <dir>` 指定要将文件安装到的磁盘目录。参数可以是相对路径或绝对路径。
<br><br><br>
# 生成器表达式
> 生成器表达式(Generator Expression),参见[^1] [^2]
生成器表达式是在 "**CMake ==配置阶段==定义**",但在 "**==构建阶段动态求值==**" 的语法,会根据**根据目标的构建类型、特定编译器、平台等条件** 得到**特定于构建系统**的结果。
### 生成器表达式类型
CMake 提供了很多种 **生成器表达式**,详细说明参见官方文档[^1],这里只列举几个:
| 类型 | 示例 |
| --------- | -------------------------------------------------------------------------------------------------------------------------- |
| | |
| **条件表达式** | - `
lt;condition:true_string>` <br>- `lt;IF:condition, true_value, false_value>` |
| **比较表达式** | - `lt;BOOL:var>`、`lt;EQUAL:a,b>`,用于值判断 |
| **配置表达式** | - `lt;CONFIG>`:返回当前配置名称<br>- `lt;CONFIG:Debug>`、`lt;CONFIG:Release>`,若设置的构建类型匹配指定值,则返回 1,否则返回 0 |
| **目标信息** | - `lt;TARGET_EXISTS:tgt>`:判断目标是否存在;<br>- `lt;TARGET_PROPERTY:tgt,property>`:返回目标属性 |
| **编译器信息** | - `lt;CXX_COMPILER_VERSION>`,`lt;CXX_COMPILER_ID>` 返回编译器版本, 名称 <br>- `lt;CXX_COMPILER_ID:..>`,若编译器名与指定列表`...` 匹配则返回 1,否则返回 0 |
| **路径表达式** | `lt;TARGET_FILE:target>`,获取目标路径 |
### 条件表达式
包括以下几种:
- (1)`lt;condition:true_string>`:当 `condition` 为 1 时生成指定字符串,为 0 时生成**空字符串**。
- (2)`lt;IF:condition, true_string, false_string>`:当 `condition` 为 1 和 0 是,**分别生成指定字符串**(since 3.8 版本)
通常使用时,`condition` 本身就会是一个生成器表达式。
> [!example] 使用示例
>
> ```cmake
> # `lt;CONFIG:Debug>`是一个"配置表达式", 仅在当前配置为 Debug 时为真.
> # `DEBUG_BUILD`宏定义仅在 Debug 配置中被添加.
> target_compile_definitions(my_target PRIVATE
> lt;lt;CONFIG:Debug>:DEBUG_BUILD>
> )
> ```
>
### 比较表达式
| 比较表达式 | 说明 |
| -------------------------------- | ---------------------------------- |
| `lt;STREQUAL:string1,string2>` | 判断字符串是否相等 |
| `lt;EQUAL:value1,value2>` | 判断数值是否相等 |
| `lt;IN_LIST:string,list>` | 判断 string 是否包含在 list 中,list 使用分号分割 |
| `lt;VERSION_LESS:v1,v2>` | 若版本号 v1 小于 v2 则为 1,否则 0 |
| `lt;VERSION_GREATER:v1,v2>` | 若版本号 v1 大于 v2 则为 1,否则 0 |
| `lt;VERSION_EQUAL:v1,v2>` | 判断版本号是否相等 |
| `lt;VERSION_LESS_EQUAL:v1,v2>` | 若版本号 v1 <= v2 则为 1,否则 0 |
| `lt;VERSION_GREATER_EQUAL:v1,v2>` | 若版本号 v1 >= V2 则为 1,否则 0 |
### 编译器相关
示例:
```cmake
# 设置包含目录为`/opt/include/GNU` 或 `/opt/include/Clang`,取决于所用的具体编译器。
target_include_directoreis(tgt PRIVATE /opt/include/lt;CXX_COMPILER_ID>)
# 如果`CMAKE_CXX_COMPILER_VERSION`低于4.2.0, 下述表达式将展开为`OLD_COMPILER`.
target_compile_options(tgt PRIVATE
lt;<$VERSION_lESS:lt;CXX_COMPILER_VERSION>,4.2.0>:OLD_COMPILER>
)
```
<br><br><br>
# list 命令
CMake 中,`list()` 函数用于操作 **列表** 变量(实际上是**分号分隔的字符串**)。
常用的 `list()` 操作:
- `list(APPEND varlist item...)` :向列表中添加一个或多个元素
- `list(FIND varlist item...)` :在列表中查找一个元素,并返回其索引
- `list(LENGTH varlist variable)` : 获取列表长度,赋给变量
- `list(GET varlist item...)` :从列表中获取一个或多个元素
- `list(REMOVE_ITEM varlist item...)` :从列表中移除一个或多个元素
- `list(INSERT varlist item...)` :在列表中的指定位置插入一个元素
- `list(SORT varlist)` :对列表进行排序
- `list(JOIN varlist sep outputVar)`:以分隔符 `sep`连接列表元素,输出合并后的字符串到 `outputVar` 变量中。
```cmake
# 定义了一个变量"SOURCE_FILES".
set(SOURCE_FILES main.cpp util.cpp bb.cpp)
# 向列表中添加文件
list(APPEND SOURCE_FILES more.cpp another.cpp)
# 获取列表长度并打印
list(LENGTH SOURCE_FILE SOURCE_FILES_LENGTH) # 获取列表长度, 赋给SOURCE_FILES_LENGTH变量
message("Number of source files: ${SOURCE_FILES_LENGTH}")
# 追加模块搜索路径
list(APPEND CMAKE_MODULE_PATH /path/to/cmake/modules)
# 以" "字符拼接列表中各项值, 赋给变量CMAKE_CXX_FLAGS.
set(CXX_FLAGS
-g
-Wall
-Wextra
-Werror
)
list(JOIN CXX_FLAGS " " CMAKE_CXX_FLAGS)
```
<br><br><br>
# file 命令
`file` 是一个多功能命令,用于文件操作。可以执行诸如读写文件、复制文件、删除文件、生成路径等多种操作。
`file` 专用于需要访问文件系统的文件、路径相关操作。
> 
### 使用示例
使用示例:
```cmake
# 命令形式:
file(<operation> <arg>...)
# 创建目录
file(MAKE_DIRECTORY [dir1 [dir2 [...]]])
# 复制文件
file(COPY src DESTINATION dest)
# 删除文件
file(REMOVE [file1 [file2 [...]]])
# 读取"文件内容"到变量
file(READ filename variable)
# 将字符串写入文件
file(WRITE filename "content to write")
# 下载文件
file(DOWNLOAD url file [options...])
```
示例二:**获取文件名列表**
```cmake
file(GLOB LIB_SOURCE lib/*.cpp)
file(GLOB LIB_HEADER lib/*.h)
add_library(MyLib ${LIB_SOURCE} {$LIB_HEADERS})
# 注意: `aux_source_directory(<dir> <VAR>)`命令只会查找指定目录下的"源文件"
# 而file()命令将根据"指定的匹配模式"(可指定多个)进行查找, 例如:
file(GLOB SRCS *.c *.cpp *.cc *.h *.hpp)
add_executable(MyExe ${SRCS})
```
示例三:遍历所有 .cpp 文件,**为每个源文件创建单独的可执行文件**
```cmake
# If necessary, use the RELATIVE flag, otherwise each source file may be listed
# with full pathname. RELATIVE may makes it easier to extract an executable name
# automatically.
# file(GLOB APP_SOURCES RELATIVE app/*.cxx)
file(GLOB PROJECT_SOURCE project/*.cxx)
foreach(testsourcefile ${PROJECT_SOURCE})
# 指定测试名称(将${testsourcefile}变量值中的".cpp"后缀替换为""即去掉), 赋给testname变量
# string(REPLACE ".cpp" "" testname ${testsourcefile})
# 或者使用如下语句提前文件名(`NAME_WE`模式: 不带目录, 不带扩展)
# get_filename_component(<var> <FileName> <mode> [CACHE])
get_filename_component(EXECUTABLE_NAME ${testsourcefile} NAME_WE)
# 添加可执行目标
add_executable(${testname} ${testsourcefile})
# 为每个目标链接到测试库
target_link_libraries(${testname} lib_dir)
# Copy required DLLs to the target folder
add_custom_command(
TARGET ${EXECUTABLE_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/Resources/Libraries/glew/bin/glew32.dll" "${CMAKE_BINARY_DIR}/glew32.dll")
endforeach(testsourcefile ${PROJECT_SOURCE})
```
<br><br><br>
# configure_file 命令
命令:`configure_file(<input> <output>)`
该命令将复制**输入文件**,并使用当前变量值 **替换掉==输入文件内容中的变量引用==**(`@VAR@`、`${VAR}`、`$CACHE{VAR}` 或`$ENV{CAR}` ),然后**输出为指定文件**。
如果没有定义变量,则替换为空字符串。
##### 使用 `CMakeLists.txt` 中定义的变量替换 "源代码中的变量引用"
基于 `configure_file()`命令,引入一个 "**配置头文件**"(configured header file) 作为输入文件,**输出文件即为所需**。
示例:在 `CMakeLists.txt` 中指定版本号并自动**替换源文件中的变量**。
- (1)**在 `CMakeLists.txt` 指定项目名和版本号**
- CMake 在处理 `project(MyProject, VERSION 1.0)` 命令后,将会在后台定义两个变量:
`<PROJECT-NAME>_VERSION_MAJOR` 和 `<PROJECT-NAME>_VERSION_MINOR`。
- (2)引入一个 "**配置头文件**",**以`.h.in` 为后缀**,**在其中使用 `@VAR@` 语法引用 CMake 变量**,见下例。
- (3)**在 `CMakeLists.txt` 中使用 `configure_file` 命令,指定==待配置的输入文件==,以及替换后的输出文件名**。
- 使用命令 `configure_file(MyProjectConfig.h.in MyProjectConfig.h)`;
- 编译过程中,CMake 将复制输入文件,并使用指定的 CMake 变量替换其中对应的变量占位符,然后输出到项目构建目录中。
- (4)在 `CMakeLists.txt` 中指定**编译给定目标时要使用的包含目录 (`#include` 查找目录)**
- 使用命令 `target_include_directories(MyProgram PBULIC "${PROJECT_BINARY_DIR}")` ;
- `configure_file()` 将输出文件到当前项目目录,因此再使用该命令将将**项目目录**( `"${PROJECT_BINARY_DIR}"`)添加到编译时查找的包含目录中。
> [!example]
> 配置头文件: `MyProjectConfig.h.in`:
>
> ```cpp
> // MyProjectConfig.h.in
> // the configured options and settings for project.
> #define MyProject_VERSION_MAJOR @MyProject_VERSION_MAJOR@
> #define MyProject_VERSION_MINOR @MyProject_VERSION_MINOR@
> ```
>
> 执行 CMake 构建后,将会生成 `MyProjectConfig.h` 头文件,其中内容被替换为
>
> ```cpp
> // MyProjectConfig.h.in
> // the configured options and settings for project.
> #define MyProject_VERSION_MAJOR 1
> #define MyProject_VERSION_MINOR 0
> ```
>
<br><br><br>
# add_custom_command 自定义命令
`add_custom_command()` 用于为 "**指定输出文件**" 绑定 **==构建阶段==生成该文件时** 所需执行的自定义命令。
> [!NOTE] 执行触发时机: 仅当其**由 `OUTPUT` 指定的输出文件 "==在构建阶段被需要==" 时(例如作为构建目标的依赖项时)**
```cmake
# 命令说明
add_custom_command(
OUTPUT <输出文件>
COMMAND <执行命令>
[DEPENDS <依赖项>]
[COMMENT "说明文本"]
[WORKING_DIRECTORY <执行目录>]
[VERBATIM]
)
```
| 选项 | 说明 |
| ------------------- | ------------------------------------------------- |
| `OUTPUT` | 指定**生成的目标文件**(如果该文件**已存在,则不会重复执行命令**) |
| `COMMAND` | 要执行的命令 |
| `DEPENDS` | 指定该命令依赖的文件或目标(如果依赖项更新,则重新执行命令) |
| `COMMENT` | 在 `make` 执行时显示的提示信息 |
| `WORKING_DIRECTORY` | 指定命令执行的目录 |
| `VERBATIM` | 确保 `COMMAND` 参数的字符串被 CMake 解析为单个参数(避免 shell 转义问题) |
> [!example] 使用示例:为多个目录下的所有头文件在指定 `src/include/` 目录下创建符号链接.
>
> ```cmake
> set(SRC_DIR "${PROJECT_SOURCE_DIR}/src")
>
> set(SUB_SRC_DIRS
> base
> acceptor
> channel
> epoller
> eventloop
> logger
> timer
> )
>
> # 为所有头文件在src/include/目录下创建符号链接.
> set(LINK_DIR "${SRC_DIR}/include")
> file(MAKE_DIRECTORY ${LINK_DIR})
>
> set(SYMLINKS "")
> foreach(D ${SUB_SRC_DIRS})
> set(INC_DIR "${SRC_DIR}/${D}")
> file(GLOB HEADER_FILES "${INC_DIR}/*.h") # 查找目录下所有头文件.
> foreach(HEADER ${HEADER_FILES})
> get_filename_component(HEADER_NAME ${HEADER} NAME) # basename文件名
> set(SYMLINK_PATH "${LINK_DIR}/${HEADER_NAME}")
>
> # 为${SYMLINK_PATH}文件绑定该自定义命令
> # 当该文件在构建阶段需要被生成时, 执行下列自定义命令.
> add_custom_command(
> OUTPUT ${SYMLINK_PATH}
> COMMAND ${CMAKE_COMMAND} -E remove -f ${SYMLINK_PATH} # 移除旧链接
> # 使用 cmake -E create_symlink 生成符号链接(跨平台)
> COMMAND ${CMAKE_COMMAND} -E create_symlink ${HEADER} ${SYMLINK_PATH}
> COMMENT "Creating symlink: ${SYMLINK_PATH} -> ${HEADER}"
> VERBATIM
> )
> list(APPEND SYMLINKS ${SYMLINK_PATH})
> endforeach()
> endforeach()
>
> # 构建create_symlinks目标时, 为${SYMLINKS}中每个${SYMLINK_PATH}执行上述自定义命令.
> add_custom_target(create_symlinks ALL DEPENDS ${SYMLINKS})
> ```
>
<br><br><br>
# 参考资料
其它介绍快速上手的博客:
[Where does CLion store executable files?](https://stackoverflow.com/questions/25836027/where-does-clion-store-executable-files)
[CMake之add_executable](https://blog.csdn.net/MacKendy/article/details/122549819)
[Using CMake add_executable To Create C++ Programs](https://matgomes.com/cmake-add-executable/)
[CMake学习笔记三:cmake 常用指令](https://www.cnblogs.com/linuxAndMcu/p/10675971.html) :star:
[CMake在Windows环境下Visual Studio Code的使用](https://www.cnblogs.com/dapenson/p/17135239.html)
# Footnotes
[^1]: [cmake 生成器表达式 (7) — CMake 3.26.4 Documentation](https://cmake-doc.readthedocs.io/zh-cn/latest/manual/cmake-generator-expressions.7.html)
[^2]: [CMake应用:生成器表达式](https://zhuanlan.zhihu.com/p/437404485)
[^3]: [Integrating Google Test Into CMake Projects](https://matgomes.com/integrate-google-test-into-cmake/)
[^4]: [FindGTest](https://cmake.org/cmake/help/latest/module/FindGTest.html)