%% # 纲要 > 主干纲要、Hint/线索/路标 # Q&A #### 已明确 #### 待明确 > 当下仍存有的疑惑 **❓<font color="#c0504d"> 有什么问题?</font>** %% # CMake 中的构建目标 > 参见 [^1] CMake 中构建目标实际生成的二进制文件包括:**可执行文件、静态库、共享库、模块库、Object 库**。 CMake 把这些构建目标分成了**三类**: | 生成文件类型 | Runtime Target | Library Target | Archive target | | ------------------------------------------------------------------------------ | -------------- | -------------- | -------------- | | 由 `add_executable()` 定义的**可执行文件** | ✔️ | | | | | | | | | 非 DLL 平台上,由 `add_library(... SHARED ...)` 定义的**动态库**的**库文件**(`.so` 或 `.dylib`) | | ✔️ | | | 在 **DLL 平台**上,由 `add_library(... SHARED ...)` 定义的**动态库**的**库文件**(`.dll`) | ✔️🚨 | | | | 在 **DLL 平台**上,由 `add_library(... SHARED ...)` 定义的**动态库**的**导入库文件**(`.lib`) | | | ✔️ | | | | | | | 由 `add_library(... STATIC ...)` 定义的**静态库**的静态库文件(`.lib` 或 `.a`) | | | ✔️ | | 由 `add_library(... MODULE ...)` 定义的**模块库**的可加载模块文件(`.dll` 或 `.so`) | | ✔️ | | > [!summary] > **DLL 平台**:包括 Cygwin 在内的**所有基于 windows 的系统**。 > > - Unix-like系统下的**动态库文件 `.so`** 被归为 **Library 目标**; > - Windows下的动态库文件 `.dll` 被归为 **==Runtime 目标==**,其导入库文件`.lib` 被归为 **Archive 目标**; > 这几种目标的**输出目录**和**输出名**由以下变量 or 属性控制: | 指定文件生成路径的==变量== | 指定生成文件名称的==目标属性== | | -------------------------------- | --------------------- | | `CMAKE_RUNTIME_OUTPUT_DIRECTORY` | `RUNTIME_OUTPUT_NAME` | | `CMAKE_LIBRARY_OUTPUT_DIRECTORY` | `LIBRARY_OUTPUT_NAME` | | `CMAKE_ARCHIVE_OUTPUT_DIRECTORY` | `ARCHIVE_OUTPUT_NAME` | ## 伪目标 CMake 中的伪目标(Pseudo Targets)是指并不会作为构建系统的输出,而只供 CMake 内部使用的**特殊构建目标**。 例如用于 **引入外部依赖**、**设置目标别名**、**或用于封装接口属性和编译参数**。 CMake 中的伪目标包括三类: - **导入目标**(Imported Targets),包括: - 导入库:由 `add_library(<name> <type> IMPORTED [GLOBAL])` 创建 - 导入可执行文件: 由`add_executable(<name> IMPORTED [GLOBAL])` 创建 - **别名目标**(Alias Targets),包括: - 别名可执行文:由 `add_executable(<name> ALIAS <target>)` 创建 - 别名库:由 `add_library(<name> ALIAS <target>)` 创建 - **接口库**(Interface Libraries) - 由 `add_library(<my_interface_lib> INTERFACE)` 创建 <br><br><br> # 创建可执行目标 CMake 中的可执行目标包括三种:Normal Executables、Imported Executables、Alias Executables[^1]。 #### 常规可执行目标 定义方式: ```cmake add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] [source1] [source2 ...]) ``` 参数说明: | | | | ----------------------- | --------------------------------------------------------- | | name | 最终生成的可执行文件名 | | WIN32 | 在 Windows 上创建 **无控制台** 的 GUI 应用(隐藏 `cmd` 窗口,仅适用于 Windows) | | MACOSX_BUNDLE | 在 macOS 上创建 `.app` 结构的应用(适用于 macOS GUI 应用) | | EXCLUDE_FROM_ALL | 不默认构建此可执行文件(仅手动构建时才编译) | | `source1 [source2 ...]` | 可执行文件的源代码文件列表 | > [!example] 示例 > > ```cmake > cmake_minimum_required(VERSION 3.10) > project(MyExecutable) > > add_executable(MyApp main.cpp utils.cpp helper.cpp) > ``` #### 导入可执行目标 `IMPORTED` 关键字表明 **引用==外部的可执行文件==**。 通过将外部可执行文件引入为 "`Imported`可执行目标 ",该目标在项目中会**被视作为 CMake 构建项目的一部分**而使用, 但**该目标不会被 CMake 构建**,CMake 将**直接引用该可执行文件**。 定义方式: ```cmake add_executable(<name> IMPORTED [GLOBAL]) ``` 导入可执行目标相关属性: - `IMPORTED` 属性: 导入库的该属性值为 `True` - `IMPORTED_LOCATION` 属性:用以指定 **二进制库文件所在的路径**。 - `IMPORTED_IMPLIB` 属性:对于 Windows 上的动态库 (`.dll`),该属性指定其导入库(`.lib`)所在的路径, 而 `IMPORTED_LOCATION` 则是指定该动态库本身(即运行时库 `.dll`) 所在的路径。 使用示例: ```cmake add_executable(my_executable IMPORTED) set_target_properties(my_executable PROPERTIES IMPORTED_LOCATION "/path/to/my_executable.out") # 示例: 导入Clang-Format add_executable(ClangFormat IMPORTED) set_property(TARGET ClangFormat PROPERTY IMPORTED_LOCATION "/path/to/clang-format" ``` #### 为可执行目标设置别名 ```cmake add_executable(<name> ALIAS <target>) ``` 注意: - ALIAS 目标可用作**被链接的目标**,也可用作**供从中读取属性的目标**。可使用常规 `if(TARGET)` 子命令测试它们是否存在。 - **ALIAS 目标不可修改、不可安装、不可导出**。 - 别名 `<name>` 不能用于修改原目标 `<target>`的属性,即不能作为`set_property()`、`set_target_properties()`、`target_link_libraries()` 等的操作数。 #### 自定义目标 通过 `add_custom_target` 可以**设置自定义的伪目标**,为该目标指定**多个依赖项**。 **当构建该伪目标时,将递归构建所有依赖项**: `cmake --build . --target my_apps`,将**构建生成伪目标 `my_apps` 的依赖项**。 示例: ```cmake add_executable(app1 src/app1.cpp) add_executable(app2 src/app2.cpp) add_executable(app3 src/app3.cpp) # 定义一个 "my_apps" 目标,依赖于 app1, app2, app3 add_custom_target(my_apps DEPENDS app1 app2 app3 ) ``` <br><br><br> # 创建库目标 库创建命令: ```CMake add_library(<name> [STATIC | SHARED | MODULE | OBJECT | INTERFACE | IMPORTED | ALIAS] [source1] [source2] ...) ``` 由关键字指定**创建不同类型的库** [^1]: | 选项 | 说明 | | ----------- | ----------------------------------------- | | `STATIC` | **静态库**(缺省默认) | | `SHARED` | **共享库**(动态库) | | `MODULE` | **模块库**(特殊的共享库,**不会被链接**,仅供动态加载) | | `OBJECT` | 对象库(仅编译生成 `.o` 目标文件,**不生成最终的库文件**) | | `INTERFACE` | 接口库(不生成任何文件,仅作为接口,**为链接至该库的目标提供编译属性和选项**) | | `ALIAS` | 用于为已有目标,创建别名,作为**既有目标的引用** | | `IMPORTED` | 导入一个**已预编译的外部库** | #### 静态库与共享库 示例: ```cmake # 通过`STATIC`关键字显式指定生成静态库(默认). add_library(archive STATIC archive.cpp zip.cpp lzma.cpp) # 通过`SHARED`关键字指定生成动态库 add_library(archive SHARED archive.cpp zip.cpp lzma.cpp) ``` #### 模块库 `MODULE` 库是一种 **==特殊的动态库==**,其**不会在编译时被链接**,而是 **运行时通过特定机制手动加载**(例如 Linux 下通过调用 `dlopen()`)。 模块库通常用于实现 **插件系统**,支持应用程序在 **运行时动态加载模块** 而无需编译时链接。 创建模块库: ```cmake # 通过`MODULE`关键字指定生成模块库 add_library(my_plugin MODULE source1.cpp source2.cpp) ``` #### Object 库 Object 库**不会生成的独立的库文件**,而是 **仅编译生成目标文件**(`.o` 或 `.obj`),可 **==供其他目标链接==**。 目标库的**生成文件是目标文件**,当其它目标(可执行文件或库)**链接到一个目标库** 后,这些生成文件将在构建时**被其它目标复用**。 > [!NOTE] 相当于执行 `gcc -c`,产出**可重定位目标文件**。 > > 作用如下: > > - (1)**优化构建时间**:对于不常改动的源文件,为其生成 `.o` 目标文件,**可避免每次重复编译**。 > - (2)**代码复用**:若多个构建目标(例如几个不同的库或可执行文件)需使用**一系列相同的源文件**,则可为这些源文件生成 `.o` 目标文件,**以供复用**。 创建 Object 库:`add_library(<libraryName> OBJECT [<source_files>...])` 使用示例: ```cmake # 通过`OBJECT`关键字定义目标库. # 其它目标链接到该目标库时, 将能够复用"该目标库关联的源文件". add_library(archive OBJECT archive.cpp zip.cpp lzma.cpp) # 示例一: 其它目标(库文件)链接到已定义的OBJECT库`archive` add_library(archiveExtras STATIC extras.cpp) target_link_libraries(archiveExtras PUBLIC archive) # 上述两行命令可通过生成器表达式`lt;TARGET_OBJECTS:name>`简化为: # add_executable(archiveExtras STATIC lt;TARGET_OBJECTS:archive> extras.cpp) # 示例二: 其它目标(可执行文件)链接到已定义的OBJECT库`archive` add_executable(test_exe test.cpp) target_link_libraries(test_exe archive) # 上述两行命令可通过生成器表达式`lt;TARGET_OBJECTS:name>`简化为: # add_executable(test_exe lt;TARGET_OBJECTS:archive> test.cpp) ``` #### Interface 库 接口库通常**不包含源文件,只用于==提供接口属性==**,如头文件目录、编译选项、链接库等,**当其它目标链接到该接口库时,将获得该库的接口属性**。 **其仅传递编译信息而不实际编译源代码**,不会生成任何编译输出。 创建接口库:`add_library(<my_interface_lib> INTERFACE)` 使用示例: ```cmake # 使用`INTERFACE`关键字, 定义接口库 # my_interface_lib 是一个接口库,不具有源文件。 # 该接口库具有一个包含目录和一个编译定义,当其他目标链接到这个库时,这些属性将被使用。 add_library(my_interface_lib INTERFACE) target_include_directories(my_interface_lib INTERFACE include/) target_compile_definitions(my_interface_lib INTERFACE SOME_DEFINITION) ``` > [!NOTE] 注:CMake 3.19 版本起,接口库可以**拥有源文件**。 > > 两种添加方式: > > - (1)源文件可以直接在 `add_library()` 调用中列出,命令如下:`add_library(<name> INTERFACE [<source>...] [EXCLUDE_FROM_ALL])` > - (2)或稍后通过调用 `target_sources()` 以及 `PRIVATE` 或 `PUBLIC` 关键字添加。 > > 如果接口库设置了源文件(`SOURCES`属性被设置)或头文件集(`HEADER_SETS`属性被设置), > 则**该接口库将作为构建目标出现在生成的 buildsystem 中**,就像`add_custom_target()` 命令定义的目标一样。 > > 但是,**该接口库==不编译任何源代码==**,只会包含由 `add_custom_command()` 命令创建的自定义命令的构建规则。 #### Imported 导入库 `IMPORTED` 关键字表明 **==引用==外部既有的预编译的库**,例如 `.so`、`.a` 或 `.lib`。 当外部库直接提供了已预编译好的二进制库文件时,就**由该方式引入当前 CMake 项目**,**CMake 将直接引用库的二进制文件**。 导入外部库: `add_library(<name> <type> IMPORTED [GLOBAL])`, 其中`<type>` 参数为 `STATIC`, `SHARED`, `MODULE`, `UNKNOWN` ,`OBJECT`,`INTERFACE` 之一。 导入库相关属性: - `IMPORTED` 属性: 导入库的该属性值为 `True` - `IMPORTED_LOCATION` 属性:用以指定库的**二进制库文件所在的路径**。 - `IMPORTED_IMPLIB` 属性:对于 Windows 上的动态库 (`.dll`),该属性指定其导入库(`.lib`)所在的路径, 而 `IMPORTED_LOCATION` 则是指定该动态库本身(即运行时库 `.dll`) 所在的路径。 使用示例: ```cmake # 使用`IMPORTED`关键字, 定义导入库 # my_imported_lib 被定义为一个 Imported 库,其二进制文件位于指定的路径。 # `IMPORTED_LOCATION` 属性用以指定库文件的路径 add_library(my_imported_lib IMPORTED) set_target_properties(my_imported_lib PROPERTIES IMPORTED_LOCATION "/path/to/libmy_imported_lib.so") ``` #### Alias 库别名 `ALIAS` 关键字用以为一个既有 **库目标** 设置**别名**。 - ALIAS 目标可以用作可链接的目标,也可以用作从中读取属性的目标,还可使用常规 `if(TARGET)` 子命令测试其是否存在。 - **ALIAS 目标不可修改、不可安装、不可导出**。 - 别名 `<name>` 不能用于修改原目标 `<target>`的属性,即不能作为`set_property()`、`set_target_properties()`、`target_link_libraries()` 等的操作数。 使用示例: ```cmake # 使用`ALIAS`关键字, 定义别名库 add_library(<name> ALIAS <target>) # 使用示例: # 1. 为lib1目标创建别名目标`UpStream::lib1` add_library(lib1 lib1.cpp) install(TARGETS lib1 EXPORT lib1Export ${dest_args}) install(EXPORT lib1Export NAMESPACE Upstream:: ${other_args}) add_library(Upstream::lib1 ALIAS lib1) # 2. 在另一个目录中,可链接到Upstream::lib1目标. if (NOT TARGET Upstream::lib1) find_package(lib1 REQUIRED) endif() add_executable(exe1 exe1.cpp) target_link_libraries(exe1 Upstream::lib1) ``` > [!example] 可通过读取目标的 **`ALIASED_TARGET` 属性** 判断该目标是否为一个**别名目标**: > > 如果该目标为别名目标,则该属性的值为**原目标的名称**。 > > ```cmake > # 读取`Upstream::lib1`目标的`ALIASED_TARGET`属性值, 赋给变量_aliased. > get_target_property(_aliased Upstream::lib1 ALIASED_TARGET) > # 根据变量`_aliased`判断该目标是否为别名目标 > if(_aliased) > message(STATUS "The name Upstream::lib1 is an ALIAS for ${_aliased}.") > endif() > ``` > > <br><br><br> # CMake 属性(Properties) "属性" 是附加在 **CMake 目标**上的**键值对**,用于 **==指定特定目标的构建行为==**,只对该目标有效 [^1]。 CMake 中,为**特定目标**设置属性有以下几种方式(按推荐程度降序): | 设置属性 | 查看属性 | 备注 | | ------------------------------------ | --------------------------------- | ---------------------------- | | `target_xxx(TARGET ...)` 命令 | 无 | ❇️ 优先使用,可指定作用域 | | `set_property(TARGET ...)` 命令 | `get_property(TARGET ...)` | 仅当属性没有 `target_xxx` 命令支持时使用 | | `set_target_property(TARGET ...)` 命令 | `get_target_property(TARGET ...)` | 不推荐,是早期 CMake 版本中的命令 | ## (1)target_xxx 系列命令 CMake 中提供了一系列 `target_xxxx` 命令,用于为 **==指定构建目标==** 设置特定**构建行为。 例如,设置**头文件目录、编译选项** `INCLUDE_DIRECTORES`、`COMPILE_DEFINITIONS`、`COMPILE_OPTIONS` 等变量,会**覆盖**诸如 `include_directories()`、`link_libraries()`、`compile_definitions()`,`compile_options()` 等全局设置。 常用命令如下: - `target_include_directories()` - `target_link_libraries()` - `target_link_options()` - `target_compile_definitions()` - `target_compile_options()` - `target_compile_features()` - `target_precompile_headers()` - `target_source()` #### 作用域控制 上述命令均可通过 `PUBLIC`、`PRIVATE`、`INTERFACE` 三个关键字 **控制 "属性或选项" 的==作用范围==**, 即指定 **为特定构建目标所设置的属性或选项** 如何 **传递** 给 **==使用该目标的其它依赖对象==**(其它库或可执行文件): - `PRIVATE`:仅对此目标有效。(**缺省默认**) - `PUBLIC`:对此目标及链接到此目标的其他目标有效。 - `INTERFACE`:仅对链接到此目标的其他目标有效。 说明: - `PUBLIC` - 作用:指定的属性或选项 **既应用于构建目标本身,也应用于链接此目标的其他目标**。 - 示例:例如,一个库的**头文件中具有其它依赖**(例如类继承),则这些相关依赖应声明为 `PUBLIC` - `PRIVATE` - 作用:指定的属性或选项 **==仅用于构建目标本身==**,不会传递给链接此目标的其他目标。 - 示例:当属性**仅对库内部实现重要**,**而对使用该库的其他目标不需要时使用**。 - 例如,实现库 A 时需要链接到其他库 B,但 A 的头文件中并不包含 B 的头文件时(仅源文件包含,例如防止头文件循环引用的场景),可将此依赖标记为 `PRIVATE`。 - 例如,实现库 A 时用到了某个特定的编译器标志,但这个标志对使用该库的目标不必要,那么可以将其标记为 `PRIVATE`。 - `INTERFACE` - 作用:指定的属性或选项不应用于目标本身,而是==**仅应用于链接此库的目标**==。 - 示例:当**库需要向使用它的目标传递接口**,但这些接口**对库自身的构建不必要时**使用。 #### 使用示例 > [!example] 示例一: > > ```cmake > # 任何链接了`mylib`目标的目标都会自动包含`/include`目录,而`/src`目录只在构建`mylib`时使用. > add_library(mylib libmain.cpp) > > target_include_directories(mylib > PUBLIC > "${PROJECT_SOURCE_DIR}/include" > PRIVATE > "${PROJECT_SOURCE_DIR}/src" > ) > ``` > > [!example] 示例二: > > ```cmake > add_library(MyLib STATIC mylib.cpp) > > # 添加 include 目录(可控制作用域) > target_include_directories(MyLib PUBLIC include) > > # 设置编译选项 > target_compile_options(MyLib PRIVATE -Wall -Wextra) > > # 添加编译定义 > target_compile_definitions(MyLib PRIVATE USE_FEATURE=1) > > # 目标链接 > target_link_libraries(MyApp PRIVATE MyLib) > ``` > > [!example] 示例三:宏定义传播示例 > > ```cmake > # 标记 INTERFACE, 使用 archive.lib 库的目标将会定义宏"USING_ARCHIVE_LIB" > add_library(archive archive.cpp) > target_compile_definitions(archive INTERFACE USING_ARCHIVE_LIB) > > # 标记 INTERFACE, 使用 serialization 库的目标将会定义宏"USING_SERILIZATION_LIB" > add_library(serialization serialization.cpp) > target_compile_definitions(serialization INTERFACE USING_SERILIZATION_LIB) > > # archiveExtras is compiled with -DUSING_ARCHIVE_LIB and -DUSING_SERIALIZATION_LIB. > add_library(archiveExtras extras.cpp) > target_link_libraries(archiveExtras > PUBLIC archive > PRIVATE serialization > ) > > # 1. consumer 依赖于 archiveExtras 库, 而前者 PUBLIC 依赖于 archive 库, 因此 archive 库的"INTERFACE"下的宏"USING_ARCHIVE_LIB"会传递给 consumer. > # 即consumer编译时会有`-DUSING_ARCHIVE_LIB`. > # 2. archiveExtras 库是"PRIVATE"地使用 serialization 库, 因此后者的宏只会给到 archiveExtras, 不会传递给 consumer. > add_executable(consumer consumer.cpp) > target_link_libraries(consumer archiveExtras) > ``` > <br> ## (2) `set_property()` 与 `get_property()` 命令 ##### 设置属性 ```cmake # 设置属性 set_property(<GLOBAL | DIRECTORY [<dir>] | TARGET [<target1> ...] | SOURCE [<src1> ...] [DIRECTORY <dirs> ...] [TARGET_DIRECTORY <targets> ...] | INSTALL [<file1> ...] | TEST [<test1> ...] [DIRECTORY <dir>] | CACHE [<entry1> ...] > [APPEND] [APPEND_STRING] PROPERTY <name> [<value1> ...]) ``` 参数: - 首项参数指定属性的**作用域**,可选项如下: - `GLOBAL`、`DIRECTORY [<dir>]`、`TARGET <target`、`SOURCE <source>`、`INSTALL <file>` 、`TEST <test>`、`CACHE <entry>` 或 `VARIABLE` 。 - `PROPERTY <name> [<value1> ...]`:指定设置的属性的名称以及值(可选)。 - `APPEND` 关键字: - 使用时,则给定的属性值将以"**追加**"的形式添加到**指定属性**,产生**属性列表**。 - `APPEND_STRING` 关键字: - 使用时,给定的字符串将作为"**字符串**"附加到指定的**属性值上**,**即以字符串拼接的形式,而不是得到字符串列表**。 ##### 获取属性 ```cmake # 获取属性 get_property(<variable> <GLOBAL | DIRECTORY [<dir>] | TARGET <target> | SOURCE <source> [DIRECTORY <dir> | TARGET_DIRECTORY <target>] | INSTALL <file> | TEST <test> [DIRECTORY <dir>] | CACHE <entry> | VARIABLE > PROPERTY <name> [SET | DEFINED | BRIEF_DOCS | FULL_DOCS]) ``` 参数: - `<variable>`:指定**存储属性值**的变量。 - 第二项参数:指定**作用域**,必须为上述命令中的选项之一。 - `SET` :使用时,变量将被设置为一个布尔值,以指示是否使用了该属性。 - `DEFINED`:使用时,变量将被设置为一个布尔值,以指示该属性是否已经被 `define_property()` 命令定义。 - `BRIEF_DOCS`,`FULL_DOCS`:使用时,变量将被设置为一个字符串,包含被请求属性的相关文档。 #### 使用示例 ```cmake # 示例一: # 使用`APPEND`关键字为目标的`list-`属性追加值. # 或使用`APPEND_STRING`关键字为目标的`string-based`属性追加值. add_executable(a ...) add_executable(b ...) set_property( TARGET a b APPEND PROPERTY INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}" ) # 示例二: 为源文件添加属性 set_property(SOURCE src1.cpp src2.cpp PROPERTY SKIP_AUTOMOC ...) ``` <br> ## (3)`set_target_properties()` 与 `get_target_properties()` `set_target_properties()` 可视为 `set_property()` 的**特化版本**,仅针对 "target" 设置。 而后者可以定义 **作用域** scope,并且不被限制于**目标**,而是可以为**源文件** 、等**指定属性**,提供了更多选项。 使用示例: ```cmake # 设置属性 set_target_properties(target1 target2 ... PROPERTIES pro1 value1 prop2 value2...) # 获取属性 # 从目标获取属性, 该属性的值将存储到变量<VAR>中. get_target_properties(<VAR> target property) # 使用示例: 为多个目标, 添加多个属性 add_executable(a ...) add_executable(b ...) set_target_properties( a b PROPERTIES LINKER_LANGUAGE CXX FOLDER "Executable" ) ``` ## 内置属性 ##### 目标相关的属性 ```cmake RUNTIME_OUTPUT_DIRECTORY # 构建RUNTIME目标文件的输出目录。 # 如果在创建目标时设置了CMAKE_RUNTIME_OUTPUT_DIRECTORY变量,则该属性由该变量的值初始化。 RUNTIME_OUTPUT_NAME # RUNTIME目标文件的输出名称。 # 此属性指定运行时目标文件的基本名称。它覆盖OUTPUT_NAME和OUTPUT_NAME_<CONFIG>属性 ``` <br><br> # Buffer ## 闪念 > sudden idea ## 候选资料 > Read it later # ♾️参考资料 # Footnotes [^1]: [cmake-buildsystem(7)](https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#id15)