C++学习笔记—cmake的新旧方法
CMake新旧方法对比详解
一、include_directories() vs target_include_directories()
这两者的核心区别在于作用域(Scope):
1. include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 …])
作用域:目录级别
- 作用: 将指定的目录添加到当前
CMakeLists.txt
文件以及所有在它之后处理的子目录的头文件搜索路径中 - 影响: 在调用后定义的所有目标都会将这些目录添加到它们的include路径中
- 特点: 类似于”全局”设置(在当前目录及子目录范围内)
- 问题: 不够精确,可能导致目标获得不必要的包含路径
2. target_include_directories(<target> [SYSTEM] [AFTER|BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1…] [<INTERFACE|PUBLIC|PRIVATE> [items2…] …])
作用域:目标级别
- 作用: 将指定的目录添加到**特定目标
<target>
**的头文件搜索路径中 - 影响: 只有指定的
<target>
会使用这些include路径 - 关键修饰符:
PRIVATE
: 目录只用于编译<target>
自身,不会传递给依赖者PUBLIC
: 目录既用于编译<target>
自身,也会传递给依赖<target>
的其他目标INTERFACE
: 目录不用于编译<target>
自身,但会传递给依赖<target>
的其他目标
- 优点: 非常精确和清晰,为每个目标精确指定所需的头文件路径
对比总结
特性 | include_directories() |
target_include_directories() |
---|---|---|
作用域 | 目录级别 (影响当前及子目录所有后续目标) | 目标级别 (只影响指定目标) |
精确性 | 低 | 高 |
封装性 | 差 (设置是”全局”的) | 好 (设置附加到具体目标) |
现代推荐 | 否 (除非有特定全局需求) | 是 (现代CMake的核心用法) |
依赖传递 | 不直接处理 | 通过PUBLIC /INTERFACE 关键字明确控制 |
二、CMake旧方式 vs 新方式(现代CMake)
核心区别在于从基于变量和全局/目录设置转向基于目标及其属性。
1. 旧方式 (Variable-Centric, Directory-Scoped - CMake 2.x时代)
核心思想: find_package
等命令设置全局变量,手动使用这些变量配置目标
典型流程:
查找和配置库
1
2
3find_package(SomeLib REQUIRED)
# 执行FindSomeLib.cmake或旧式SomeLibConfig.cmake
# 设置变量:SomeLib_FOUND, SomeLib_INCLUDE_DIRS, SomeLib_LIBRARIES等手动应用这些变量
1
2
3
4
5
6
7# 全局设置,影响后续所有目标
include_directories(${SomeLib_INCLUDE_DIRS})
add_definitions(${SomeLib_DEFINITIONS})
add_executable(my_app main.cpp)
# 手动链接库
target_link_libraries(my_app ${SomeLib_LIBRARIES})
特点与问题:
- 全局状态: 变量和设置通常影响整个目录或项目,容易冲突
- 手动管理: 需了解每个库设置的变量,手动应用到目标的各属性
- 传递依赖困难: 如
my_app
依赖my_lib
,而my_lib
依赖SomeLib
,则my_app
的配置文件可能也需了解SomeLib
,破坏封装性 - 不够清晰: 链接指令通常只是变量引用,不明确表达依赖关系
2. 新方式 (Modern CMake / Target-Centric - CMake 3.0+时代)
核心思想: 将构建所需的所有信息附加到目标上,使用导入目标(Imported Targets)
典型流程:
查找和配置库
1
2
3find_package(SomeLib REQUIRED)
# 执行现代SomeLibConfig.cmake
# 定义导入目标,如SomeLib::Core直接链接导入目标
1
2
3
4add_executable(my_app main.cpp)
# 只需链接目标,CMake自动处理其他所有事情
target_link_libraries(my_app PRIVATE SomeLib::Core)
特点与优势:
- 目标即一切: 所有配置围绕目标进行,使用
target_*
系列命令 - 封装性: 库的使用细节被封装在导入目标中,配置文件只需知道目标名称
- 自动传递依赖: 链接导入目标时,该目标的公共依赖自动传递
- 清晰明确: 链接指令直接表明依赖关系
- 精确控制: 使用
PRIVATE
/PUBLIC
/INTERFACE
关键字精确控制依赖传递
对比总结
方面 | 旧方式 (Variable-Centric) | 新方式 (Target-Centric) |
---|---|---|
核心 | 全局/目录变量 (_DIRS , _LIBS ) |
目标及其属性 (target_* 命令, 导入目标) |
find_package |
主要设置变量 | 主要定义导入目标 (Namespace::Target ) |
配置方式 | 手动应用变量到目标 | 链接导入目标,CMake自动处理细节 |
包含路径 | include_directories() (全局) |
target_include_directories() (目标级) |
链接 | target_link_libraries(... ${..._LIBS}) |
target_link_libraries(... Namespace::Target) |
依赖传递 | 手动处理,易出错 | 自动处理 (通过PUBLIC /INTERFACE ) |
封装性 | 差 | 好 |
推荐度 | 不推荐 | 强烈推荐 |
三、实践建议
始终优先使用现代CMake方法
- 使用
target_*
系列命令而非全局设置 - 链接导入目标而非变量列表
- 使用
合理使用作用域关键字
PRIVATE
: 仅在目标内部使用,不传递给依赖者PUBLIC
: 在目标内部使用且传递给依赖者INTERFACE
: 不在目标内部使用,仅传递给依赖者
创建自己的库时
- 导出明确的目标而非变量
- 正确设置
PUBLIC
和INTERFACE
属性以确保依赖正确传递
处理旧式库时
- 可以创建接口库封装旧式变量,使其符合现代CMake风格
1
2
3
4
5add_library(SomeOldLib::SomeOldLib INTERFACE IMPORTED)
set_target_properties(SomeOldLib::SomeOldLib PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${SomeOldLib_INCLUDE_DIRS}"
INTERFACE_LINK_LIBRARIES "${SomeOldLib_LIBRARIES}"
)