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)