
<font color="#5rdtc3545" size="4" face="楷体">本文系统讲解CMake核心命令,涵盖目标创建、属性管理、依赖传递及文件收集。通过add_library、target_include_directories等命令实现模块化构建,详解PUBLIC/PRIVATE/INTERFACE关键字的依赖传递机制,并演示file(GLOB/GLOB_RECURSE)自动化文件收集技巧。</font>
add_library 命令 add_library 命令的核心作用是指示 CMake 创建一个库文件构建目标,这是管理库构建的起点。
必须通过 <name> 参数为要创建的库指定一个项目内唯一的名称(如 `add_library
(my_lib ...))。CMake 会自动根据平台添加标准前缀和后缀(如libmy_lib.a或my_lib.lib`)。
通过指定关键字来选择生成的库类型,这是决定库行为的关键操作:
STATIC:创建静态库(.a 或 .lib),库代码会直接嵌入到最终的可执行文件中。SHARED:创建动态库/共享库(.so 或 .dll),库代码在程序运行时才被加载,可被多个程序共享。MODULE:创建模块库(类似插件),运行时动态加载,但通常不参与链接。必须在命令中提供构建该库所需的源文件列表(如 add_library(my_lib file1.cpp file2.cpp))。这是生成库的原材料。
使用 EXCLUDE_FROM_ALL 选项可以控制该库是否在默认构建(如直接运行 make)时被编译。通常用于那些需要特定条件才编译的辅助库或测试库。
库文件默认生成在 CMake 当前所在的构建目录中。之后可以通过设置以下目标属性来灵活地更改输出路径:
ARCHIVE_OUTPUT_DIRECTORY:主要控制静态库(.a/.lib)的输出目录。LIBRARY_OUTPUT_DIRECTORY:主要控制动态库(.so/.dll)的输出目录。RUNTIME_OUTPUT_DIRECTORY:对于某些平台(如 Windows),DLL 有时被视为运行时组件,也可用此属性控制。一句话: add_library 命令的操作流程是:创建目标 -> 命名 -> 选择类型 -> 指定源码 -> (可选)排除默认构建,后续再通过其他命令定制输出位置。
<font color="blue">下面看下官方定义的库的选项:</font>

对应地址:官网文档
下面简单介绍下常见的选项库:
库类型 | 核心用途 | 一句话用法 |
|---|---|---|
普通库 (Normal) | 构建项目内部的静态库( |
|
对象库 (Object) | 只编译不链接,生成 |
|
接口库 (Interface) | 不编译代码,只定义头文件路径等依赖供其他目标使用 |
|
导入库 (Imported) | 引用一个外部已存在的预编译库(第三方库) |
|
别名库 (Alias) | 给一个已存在的库目标起一个简短的别名或代号 |
|
==总结:==
STATIC/SHARED(普通库)或 OBJECT(对象库)。INTERFACE(接口库)。IMPORTED(导入库)。ALIAS(别名库)。target_include_directories 命令 用于为指定的构建目标(<target>)设置头文件(.h)的搜索路径。这些路径会在编译时通过 -I 参数传递给编译器(如 gcc)。
通过 PUBLIC、PRIVATE、INTERFACE 关键字(这里要知道 PUBLIC=PRIVATE+INTERFACE 根据它三设置目标对应的 INCLUDE_DIRECTORIES与INTERFACE_INCLUDE_DIRECTORIES的属性变量值),可以将头文件路径自动传递给链接该目标的其他目标,避免了手动管理全局路径的混乱。
target_include_directories(<target>
<INTERFACE|PUBLIC|PRIVATE> <path1> [<path2> ...] )SYSTEM:告知编译器该路径是系统头文件路径,编译器会使用 -isystem 而非 -I 来抑制第三方库的编译警告。BEFORE:将新路径插入到现有列表的最前面。如果提供的 path 是相对路径,则其解析基准是当前 CMakeLists.txt 文件所在的目录(CMAKE_CURRENT_SOURCE_DIR)。
文档明确推荐使用 target_include_directories 而非旧的 include_directories 命令,因为后者是全局设置,容易导致路径被错误地添加到其他不相关的目标中,造成污染和冲突。
一句话: 此命令是现代 CMake 管理头文件依赖的核心方法,它能精确、可控地为每个目标设置搜索路径,并能自动传递给下游使用者,是替代旧式全局设置的最佳实践。
下面举个例子解释下:

表明构建树)的时候查找文件去指定当前执行cmake命令的目录对应的去找。安装树如/usr/local/include);然后完成库的正常使用。target_link_libraries 命令用于为一个构建目标(库或可执行程序)指定它需要链接的依赖库列表。这是管理项目依赖关系的核心命令。
该命令的底层操作等同于使用 set_target_properties 设置目标的以下两个属性:
LINK_LIBRARIES:该目标自身需要链接的库(相当于PRIVATE)。INTERFACE_LINK_LIBRARIES:需要传递给其他依赖此目标的目标的库(相当于interface)。PUBLIC。这些被指定的库会最终转换为编译器(如 GCC)的 -l 链接器选项,从而在链接阶段被正确找到并链接。
通过三个关键字精确控制依赖的传播范围,这是现代 CMake 的精髓:
PRIVATE:依赖库仅用于当前目标自身的链接,不传播给使用者。INTERFACE:依赖库不用于当前目标自身,但要求所有使用者必须链接它们。PUBLIC:依赖库既用于当前目标自身,也传播给所有使用者。 target_link_libraries(<目标名>
<PRIVATE|PUBLIC|INTERFACE> <库名>...
[<PRIVATE|PUBLIC|INTERFACE> <库名>...]...
)一句话:
target_link_libraries 是告诉 CMake “谁需要链接哪些库”,并通过 PUBLIC/PRIVATE/INTERFACE 关键字智能地控制这些依赖关系如何传递,最终生成正确的链接器命令。
下面演示下效果:
对应测试目录:
├── CMakeLists.txt
├── main.cpp
└── src
├── add.cpp
└── CMakeLists.txt对应add.cpp这里搞成空文件;可能报错;为了演示这个效果。
src里面的cmakeLists.txt文件:

顶层cmakeLists.txt文件:

下面走到构建目录cmake ..然后 --build -v查看下构建目标详细:

PRIVATE+PUBLIC的),也就是 - I;又因为是静态库故链接库文件搜索路径就没有去。PUBLIC+INTERFACE);再次去搜索头文件和库文件(因为这里是属性传递;只要是符合这俩要求都传递;故库文件路径也去搜索了),也就是 -I -L。因此总结下:
==对于属性传递:如果是类似于main文件需要加载或者链接库add;此时就相当于加载/链接库本身+add库的一些属性接口传递+add依赖的库被设置进来的一些属性传递的接口等;最后这些关系都转化成makefile里的 gcc -I -L等。==
set_target_properties 和 get_target_property 命令这两个命令是目标属性管理的核心。set_target_properties 用于设置或修改一个或多个目标的属性,而 get_target_property 用于查询某个目标的特定属性值。
操作的对象是目标(Target),即通过 add_executable 或 add_library 创建的可执行文件或库。
set_target_properties(target1 target2 ...
PROPERTIES
prop1 value1
prop2 value2
) get_target_property(<out_var> <target> <property>)属性按其用途主要分为两大类,这是现代 CMake 的精髓:
INCLUDE_DIRECTORIES, LINK_LIBRARIES)。INTERFACE_INCLUDE_DIRECTORIES, INTERFACE_LINK_LIBRARIES)。LIBRARY_OUTPUT_DIRECTORY (库输出路径)、ARCHIVE_OUTPUT_DIRECTORY (静态库输出路径)。OUTPUT_NAME (控制输出文件名)。LINK_LIBRARIES (需要链接的库列表)。BUILD_RPATH (构建目录中的运行时库搜索路径)、INSTALL_RPATH (安装后的运行时库搜索路径)。许多属性在底层会转换为具体的编译器选项,例如:
INCLUDE_DIRECTORIES --> -ILINK_LIBRARIES -->-lBUILD_RPATH -->-Wl,-rpath总结: 这两个命令提供了直接、底层的方式来精细控制每个目标的构建行为,是实现高级构建配置的基础。通过设置不同的属性,可以控制目标的输出位置、名称、依赖关系以及如何与编译器和链接器交互,也就是我们可以通过设置目标的属性来减少一些命令的书写;与直接给它设在属性里是等同的。
下面演示下:
对应目录:
├── CMakeLists.txt
├── main.cpp
└── src
├── add.cpp
└── CMakeLists.txt这里只改动下底层cmakeLists文件:
add_library(add SHARED add.cpp)
set_target_properties(add PROPERTIES
COMPILE_OPTIONS "-g"
COMPILE_OPTIONS "-O3"
COMPILE_OPTIONS "-fPIC"
# 类似于target_include_directories
INCLUDE_DIRECTORIES "/public"
INTERFACE_INCLUDE_DIRECTORIES "/interface"
# 私有链接+接口路径 类似于target_link_directories target_link_libraries
LINK_DIRECTORIES "/public"
INTERFACE_LINK_DIRECTORIES "/interface"
LINK_LIBRARIES "curl"
INTERFACE_LINK_LIBRARIES "jsoncpp"
# 输出路径设置(相对与顶层cmakeLists.txt)
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
# 设置参数
BUILD_RPATH "${CMAKE_BINARY_DIR}/lib"#告诉编译器链接这个动态库的时候去这个路径搜索这个动态库
INSTALL_RPATH "lib" #//usr/local/lib 没有/就是相对cmake_install_prefix
OUTPUT_NAME "add"
VERSION "1.2.3"
SOVERSION "20"
)然后进行cmake ..完成makefile制作再cmake+build;构建对应目标:

build_rpath属性对应路径去搜索这个库了。
==注:==
add_subdirectory与include 命令用于将子目录添加到构建系统中。执行该命令后,CMake 会立即暂停当前 CMakeLists.txt 的处理,转而进入指定的子目录并执行其内的 CMakeLists.txt 文件,待子目录处理完毕后,再返回继续执行后续命令。
source_dir:必需。子目录名(相对于当前 CMakeLists.txt 所在目录)。
binary_dir:可选。指定子目录的构建输出路径(相对于当前构建目录)。如不指定,则默认与 source_dir 同名。局部的 比如set(key "value"),不会影响父目录的变量;但是比如add_librarise add_executable等设置属性时候就是全局设置的。set(var PARENT_SCOPE) 显式将变量传递到父目录作用域。当 CMake 进入子目录执行时,以下“CURRENT”系列内置变量的值会自动改变,以反映新的上下文:
CMAKE_CURRENT_SOURCE_DIR -> 指向子目录的源路径CMAKE_CURRENT_BINARY_DIR -> 指向子目录的构建输出路径CMAKE_CURRENT_LIST_FILE -> 指向子目录的 CMakeLists.txt 文件CMAKE_CURRENT_LIST_DIR -> 指向子目录的 CMakeLists.txt 所在目录source_dir 的路径解析基准是当前 CMakeLists.txt 文件所在的目录。binary_dir 的路径解析基准是当前的构建目录。为了获得相对于正在执行的 CMake 脚本文件的路径,建议统一使用 CMAKE_CURRENT_LIST_DIR 作为参考基准,这在 add_subdirectory 和 include 命令中均适用。
add_subdirectory与include区别对比add_subdirectory (新建):进入一个独立的子项目/模块,拥有自己的源目录和构建目录(类似进入一个新的作用域执行)。include (复用):将另一个 .cmake 脚本文件的内容插入到当前上下文中执行,不改变当前项目的源和构建目录上下文(类似于文件直接展开在当前cmakeLists.txt文件)。CMAKE_CURRENT_SOURCE_DIR, CMAKE_CURRENT_BINARY_DIR)add_subdirectory:会变化。指向子目录对应的源路径和构建路径。include:不变化。保持指向父目录的源路径和构建路径。CMAKE_CURRENT_LIST_FILE, CMAKE_CURRENT_LIST_DIR)CMakeLists.txt)及其所在目录。add_subdirectory:用于管理大型项目的模块化子组件(如一个独立的库或应用),每个子目录通常是一个相对独立的构建目标。include:用于代码复用和模块化配置,通常用于包含函数定义、宏、或通用的配置片段(.cmake 脚本文件)。一句话:
add_subdirectory 来添加一个需要独立构建的子项目(会改变源和构建目录)。include 来插入并执行一段通用的配置脚本(不改变源和构建目录,只改变当前执行的文件上下文)。CMAKE_BINARY_DIR;这个无论在这俩哪里都是基于顶层cmakeLists.txt为基准构建的。==下面验证下:==
就分别打印上面说的四个变量:
<font color="#FF5733">顶层cmake文件与子目录的底层cmake文件:</font>
底层cmake文件:

顶层cmake文件:

下面运行看下结果:

<font color="#5733FF">顶层cmake文件与include的cmake脚本:</font>
顶层cmake文件:

子目录cmake脚本:

效果:

file 命令用于在构建时(configure-time)匹配指定模式的文件,并将匹配到的文件路径列表存入一个变量中。常用于快速收集源文件或资源文件。
GLOB:非递归。仅匹配当前目录下符合模式的文件。GLOB_RECURSE:递归。匹配当前目录及其所有子目录下符合模式的文件。 file(GLOB <out_var> [CONFIGURE_DEPENDS] <pattern1> [<pattern2> ...])
file(GLOB_RECURSE <out_var> [CONFIGURE_DEPENDS] <pattern1> [<pattern2> ...])<out_var>:必需。用于存储匹配结果的文件列表变量(如 MY_FILES)。<pattern>:必需。通配符表达式(如 *.cpp, src/**/*.h)。** 表示递归任何子目录。[CONFIGURE_DEPENDS]:可选。推荐添加。让 CMake 在每次构建时重新检查文件列表是否有变化(如新增文件),从而自动重新运行配置,避免因文件系统变动导致构建过期。GLOB。因为如果漏掉新文件,CMake 不会自动检测到(除非加 CONFIGURE_DEPENDS),可能导致构建错误。一句话: file(GLOB) 是一个方便的“文件收集器”,但需谨慎使用,添加 CONFIGURE_DEPENDS 选项可提升其可靠性。
==下面演示下:==
测试目录:
├── c.cpp
├── CMakeLists.txt
└── sub
├── a.cpp
└── a.js下面我们使用递归模式的搜索当前目录的js与cpp文件保存在变量中;然后打印出来看看效果:

CONFIGURE_DEPENDS。效果:

<font color="purple" size="6" face="楷体">点我查看代码示例</font>
<font color="#DC3545" size="4" face="楷体">本篇将带你掌握现代CMake构建系统的核心命令与属性管理,能够高效实现项目模块化与自动化依赖管理。通过目标属性精确控制构建行为,利用依赖传递简化多模块协作,使用文件收集优化资源管理,显著提升C++项目的工程化水平与维护性。</font>
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。