%% # 纲要 > 主干纲要、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` 变量中 > > ![image-20231210225408183|655](_attachment/05-工具/CMake%20构建工具/CMakeLists.txt%20基本指令.assets/IMG-CMakeLists.txt%20基本指令-1F1126BDF00D03635988DE0B4641707E.png) > <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 内置的 "查找模块" > ![image-20231210194915134|557](_attachment/05-工具/CMake%20构建工具/CMakeLists.txt%20基本指令.assets/IMG-CMakeLists.txt%20基本指令-DF7218B759B7382F3AAF6C6BC9DACBF7.png) > [!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 > ``` > > ![image-20231210200858594|506](_attachment/05-工具/CMake%20构建工具/CMakeLists.txt%20基本指令.assets/IMG-CMakeLists.txt%20基本指令-2EB0E2EE429CAC58FE94581CCA0ABD4C.png) > <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` 专用于需要访问文件系统的文件、路径相关操作。 > ![image-20231220131816494|773](_attachment/05-工具/CMake%20构建工具/CMakeLists.txt%20基本指令.assets/IMG-CMakeLists.txt%20基本指令-E77D8C9EC49EF1F5CD3FA64622CF1E64.png) ### 使用示例 使用示例: ```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)