目录
1.include
1.1.简介
在 CMake 中,include()
是一个用于导入外部 CMake 脚本或模块的命令,类似于编程语言中的 import
或 #include
。
基本语法:
include(<file|module> [OPTIONAL] [RESULT_VARIABLE <var>] [NO_POLICY_SCOPE])
<file|module>
:要导入的文件路径或模块名。- 若为文件路径(如
cmake/Utils.cmake
),直接加载该文件。 - 若为模块名(如
FetchContent
),在 CMake 模块路径中查找并加载。
- 若为文件路径(如
OPTIONAL
:可选参数,若文件不存在则忽略错误。RESULT_VARIABLE <var>
:将加载结果存储到变量中(成功为TRUE
,失败为FALSE
)。NO_POLICY_SCOPE
:不创建新的策略作用域(CMake 3.20+)。
如果include的是模块名,该命令会依次将 CAMAKE_MODULE_PATH 列表变量中的路径作为父目录来搜索<CMake>模块,若任未能搜索到,则从CMake预置模块目录搜索指定的<CMake模块>。
引用的外部程序与该命令所在的调用上下文共享相同的作用域,也就是说,引用的程序内创建的变量,在include命令之后仍然存在。
1.2.核心作用
- 模块化 CMake 代码:将通用逻辑(如工具链配置、依赖管理)封装到独立文件中,避免重复代码。
- 导入标准模块:加载 CMake 内置模块(如
ExternalProject
、CPack
),扩展功能。 - 跨平台工具链:通过导入工具链文件(如
arm-gcc-toolchain.cmake
),支持交叉编译。
1.3.工作流程
1.路径搜索:
- 若参数是绝对路径(如
/path/to/script.cmake
),直接加载该文件。 - 若为相对路径(如
cmake/Utils.cmake
),在当前目录及其父目录中查找。 - 若为模块名(如
FindOpenCV
),在CMAKE_MODULE_PATH
中查找(通常位于$CMAKE_ROOT/Modules
)。
2.脚本执行:
- 被导入的脚本在当前 CMake 作用域中执行,定义的变量、函数、宏等直接生效。
- 若使用
NO_POLICY_SCOPE
,则不创建新的策略作用域(避免策略版本冲突)。
3.变量继承:
- 导入脚本可访问当前作用域的变量,也可通过
set(... PARENT_SCOPE)
修改父作用域变量。
1.4.常见使用场景
1.导入自定义 CMake 脚本
项目结构:
project/
├─ CMakeLists.txt
└─ cmake/
└─ Utils.cmake # 自定义工具函数
Utils.cmake
:
# 定义通用函数
# Turn on warnings on the given target
function(enable_warnings target)
if(SPDLOG_BUILD_WARNINGS)
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
list(APPEND MSVC_OPTIONS "/W3")
if(MSVC_VERSION GREATER 1900) # Allow non fatal security warnings for msvc 2015
list(APPEND MSVC_OPTIONS "/WX")
endif()
endif()
target_compile_options(
${target_name}
PRIVATE $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
-Wall
-Wextra
-Wconversion
-pedantic
-Werror
-Wfatal-errors>
$<$<CXX_COMPILER_ID:MSVC>:${MSVC_OPTIONS}>)
endif()
endfunction()
CMakeLists.txt
:
# 导入自定义脚本
include(cmake/Utils.cmake)
# 使用脚本中定义的函数
add_executable(my_app src/main.cpp)
enable_warnings(my_app) # 应用警告选项
2.导入 CMake 标准模块
# 导入 FetchContent 模块(用于依赖管理)
include(FetchContent)
# 使用模块功能
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.1.1
)
FetchContent_MakeAvailable(fmt)
# 链接依赖
target_link_libraries(my_app PRIVATE fmt::fmt)
3.导入工具链文件(交叉编译)
# 项目根目录 CMakeLists.txt
if(CROSS_COMPILE)
include(cmake/arm-gcc-toolchain.cmake) # 导入工具链
endif()
arm-gcc-toolchain.cmake
:
# 配置交叉编译工具链
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
1.5.与 add_subdirectory
的区别
特性 | include() |
add_subdirectory() |
---|---|---|
作用域 | 共享当前作用域 | 创建新的子作用域 |
文件类型 | 纯 CMake 脚本 | 完整的子项目 |
构建目标 | 不生成目标 | 可定义目标(如 add_library ) |
典型场景 | 导入工具函数、模块 | 管理子项目、源代码目录 |
1.6.注意事项
1.脚本组织
将通用逻辑封装到 cmake/
目录下的脚本中:
project/
├─ CMakeLists.txt
└─ cmake/
├─ CompilerOptions.cmake # 编译选项
├─ CodeCoverage.cmake # 代码覆盖率
└─ FindMyLib.cmake # 自定义 Find 模块
2.模块导入
使用 list(APPEND CMAKE_MODULE_PATH ...)
添加自定义模块路径:
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
include(FindMyLib) # 从 cmake/ 目录加载自定义模块
3.条件导入
根据平台或选项选择性导入脚本:
if(WIN32)
include(cmake/WindowsUtils.cmake)
else()
include(cmake/UnixUtils.cmake)
endif()
4.变量作用域
- 导入脚本中的变量默认在当前作用域生效,可能覆盖已有变量。
- 建议使用函数封装逻辑,避免全局变量污染:
# 推荐写法
function(my_function arg)
# 函数内的变量是局部的
set(result "${arg}_processed" PARENT_SCOPE)
endfunction()
5.循环导入
- 避免脚本之间的循环导入(如
A.cmake
导入B.cmake
,而B.cmake
又导入A.cmake
),会导致无限递归。
6.模块版本兼容性
某些模块(如 CPack
)依赖 CMake 版本,需确保版本兼容:
cmake_minimum_required(VERSION 3.16) # 确保支持 FetchContent
include(FetchContent)
2.include_guard
2.1.简介
include_guard()
是 CMake 3.10 引入的一个命令,用于防止 CMake 脚本被重复包含,类似于 C/C++ 头文件中的 #pragma once
或头文件保护宏(#ifndef ... #define ... #endif
)。
基本语法:
include_guard([GLOBAL|DIRECTORY])
GLOBAL
:在全局范围内检查重复包含(默认行为)。DIRECTORY
:仅在当前目录及其子目录的 CMake 脚本中检查重复包含。
2.2.核心作用
- 防止重复定义:避免在多次包含同一脚本时,重复定义函数、宏或设置变量。
- 提高性能:减少不必要的脚本执行,加速 CMake 配置过程。
- 避免循环依赖:防止脚本间的循环包含(如
A.cmake
包含B.cmake
,而B.cmake
又包含A.cmake
)。
2.3.工作原理
1.唯一标识符生成:
include_guard()
会根据当前脚本的绝对路径生成一个唯一标识符(如CMAKE_INCLUDE_GUARD_/path/to/script.cmake
)。- 该标识符存储在 CMake 的缓存变量中(
CMAKE_INCLUDE_GUARD_*
)。
2.重复检查:
- 每次执行
include_guard()
时,检查该标识符是否已存在。 - 若存在,则立即返回,跳过脚本剩余部分;否则继续执行并标记为已包含。
3.作用域选项:
GLOBAL
:在整个 CMake 项目中检查重复(默认)。DIRECTORY
:仅在当前目录及其子目录的 CMake 脚本中检查重复(适用于模块化项目)。
2.4.常见使用场景
1.通用工具函数脚本
cmake/Utils.cmake
:
include_guard() # 防止重复包含
# 定义函数
function(enable_warnings target)
target_compile_options(${target} PRIVATE
-Wall -Wextra -Wpedantic
)
endfunction()
# 定义宏
macro(add_example name)
add_executable(${name} examples/${name}.cpp)
target_link_libraries(${name} PRIVATE my_lib)
endmacro()
CMakeLists.txt
:
# 多次包含同一脚本(实际项目中可能因条件分支导致)
if(DEBUG)
include(cmake/Utils.cmake)
endif()
# 其他条件也可能包含
if(BUILD_TESTS)
include(cmake/Utils.cmake) # 第二次包含,被 include_guard 阻止
endif()
# 正常包含(仅执行一次)
include(cmake/Utils.cmake)
# 使用脚本中定义的函数
add_executable(my_app src/main.cpp)
enable_warnings(my_app)
2.第三方依赖模块
cmake/FindMyLib.cmake
:
include_guard()
# 查找库
find_library(MYLIB_LIBRARY NAMES mylib)
find_path(MYLIB_INCLUDE_DIR NAMES mylib.h)
# 设置结果
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MyLib DEFAULT_MSG
MYLIB_LIBRARY MYLIB_INCLUDE_DIR
)
2.5.include_guard()
优势
- 自动生成标识符:无需手动命名变量,避免命名冲突。
- 简洁:只需一行代码,提高可读性。
- 兼容性:在 CMake 3.10+ 版本中稳定支持。
2.6.注意事项
1.在所有共享脚本中使用
- 所有被多个地方包含的脚本(如工具函数、Find 模块)都应添加
include_guard()
。
2.放在脚本顶部
确保 include_guard()
是脚本中的第一个命令,避免在检查前执行不必要的代码。
# 正确位置
include_guard()
# 脚本内容
function(my_function)
# ...
endfunction()
3.选择合适的作用域
- 默认
GLOBAL
:适用于全局工具函数或依赖模块。 DIRECTORY
:适用于特定目录下的模块化脚本,避免与其他目录的脚本冲突。
4.版本兼容性
include_guard()
仅在 CMake 3.10+ 版本中可用。若需兼容旧版本,需手动实现保护逻辑。
5.条件包含
- 若脚本中的部分内容需要在每次包含时执行(如更新变量),需将这些内容放在
include_guard()
之后,但可能导致重复定义,需谨慎处理。
3.include_directories
3.1.简介
include_directories
是 CMake 中用于指定头文件搜索路径的命令,允许编译器在编译时找到项目依赖的头文件。
基本语法:
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
FTER|BEFORE
:指定路径添加到搜索列表的位置(默认AFTER
)。SYSTEM
:将目录标记为系统目录,减少编译警告。dir1 [dir2 ...]
:要添加的头文件搜索路径(相对路径或绝对路径)。
3.2.常见使用场景
1.简单项目
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyApp)
# 添加项目头文件目录
include_directories(include)
# 添加源文件
add_executable(my_app src/main.cpp)
2. 多目录项目
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyApp)
# 添加多个头文件目录
include_directories(
include
src/core
src/gui
)
# 添加源文件
add_executable(my_app src/main.cpp)
3.外部依赖
# 查找并链接 OpenCV
find_package(OpenCV REQUIRED)
# 添加 OpenCV 头文件目录(通常由 find_package 设置)
include_directories(${OpenCV_INCLUDE_DIRS})
# 链接 OpenCV 库
target_link_libraries(my_app ${OpenCV_LIBS})
3.3.注意事项
1.全局作用域
include_directories
设置的路径对当前 CMakeLists.txt 及所有子目录有效。- 替代方案:使用
target_include_directories
为特定目标设置路径:
target_include_directories(my_app PRIVATE include)
2.相对路径与绝对路径
- 相对路径基于当前 CMakeLists.txt 的位置。
- 建议使用绝对路径(如
${CMAKE_SOURCE_DIR}/include
)提高可移植性。
3.路径顺序
- 多个目录按添加顺序搜索,先找到的头文件优先使用。
- 使用
BEFORE
/AFTER
控制路径优先级:
include_directories(BEFORE src/override) # 优先搜索
4.优先使用 target_include_directories
- 提高代码隔离性,避免不同目标的路径冲突。
- 支持
PRIVATE
/PUBLIC
/INTERFACE
作用域控制。
5.区分项目内与第三方路径:
# 项目内头文件
target_include_directories(my_app PRIVATE include)
# 第三方库头文件(标记为 SYSTEM 减少警告)
target_include_directories(my_app SYSTEM PRIVATE third_party/include)
4.target_include_directories
4.1.简介
target_include_directories
是 CMake 中用于精确控制目标(如可执行文件、库)头文件搜索路径的命令,相比全局的 include_directories
,它提供了更细粒度的作用域控制。
基本语法:
target_include_directories(
<target> # 目标名称(如 add_executable 或 add_library 创建的)
[SYSTEM] # 将目录标记为系统目录(减少编译警告)
[BEFORE] # 优先搜索这些目录(而非追加到末尾)
<INTERFACE|PUBLIC|PRIVATE> # 作用域
[items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...]] ...
)
- 作用域选项:
PRIVATE
:仅目标自身使用这些路径。PUBLIC
:目标和依赖该目标的其他目标都使用这些路径。INTERFACE
:仅依赖该目标的其他目标使用这些路径(目标自身不使用)。
4.2.核心作用
1.模块化头文件路径:
- 为特定目标设置头文件搜索路径,避免全局污染。
- 例如:库
libA
的公开头文件路径对依赖它的项目可见,私有头文件仅内部使用。
2.构建依赖传递:
- 通过
PUBLIC
/INTERFACE
作用域,自动将头文件路径传递给依赖链上的其他目标。 - 例如:
app
→libB
→libA
,若libB
以PUBLIC
方式链接libA
,则app
也能找到libA
的头文件。
3.系统目录标记:
- 使用
SYSTEM
标记第三方库目录,减少编译器警告(如-isystem
选项)。
4.3.常见使用场景
1.库项目的公开 / 私有头文件
项目结构:
mylib/
├─ include/ # 公开头文件(供外部使用)
│ └─ mylib/api.h
├─ src/ # 源代码和私有头文件
│ ├─ impl.cpp
│ └─ detail/impl.h # 私有头文件(不暴露给外部)
└─ CMakeLists.txt
CMakeLists.txt
:
add_library(mylib STATIC src/impl.cpp)
# 公开头文件路径(供依赖 mylib 的目标使用)
target_include_directories(mylib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> # 构建时
$<INSTALL_INTERFACE:include> # 安装后
)
# 私有头文件路径(仅 mylib 自身使用)
target_include_directories(mylib
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
2.第三方依赖的系统目录标记
add_executable(my_app src/main.cpp)
# 将 fmt 库的头文件标记为系统目录(减少警告)
target_include_directories(my_app
SYSTEM PRIVATE
${FMT_INCLUDE_DIRS}
)
3.传递性依赖(多级项目)
# libA 项目
add_library(libA STATIC src/libA.cpp)
target_include_directories(libA
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include # 公开头文件
)
# libB 项目(依赖 libA)
add_library(libB STATIC src/libB.cpp)
target_link_libraries(libB PUBLIC libA) # PUBLIC 确保 libA 的头文件路径传递给依赖 libB 的目标
# app 项目(依赖 libB)
add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE libB) # app 自动获得 libA 的头文件路径
4.4.与其他命令的对比
命令 | 作用域 | 目标针对性 | 传递性 | 推荐场景 |
---|---|---|---|---|
include_directories |
全局 | 否 | 否 | 简单项目、临时测试 |
target_include_directories |
目标级 | 是 | 是 | 模块化项目、库开发 |
set_target_properties |
目标级 | 是 | 否 | 特殊属性(如 CXX_STANDARD) |
4.5.注意事项
1.分离公开 / 私有头文件
- 库项目中,将公开 API 放在
include/
目录,私有实现放在src/
目录。 - 使用
PUBLIC
暴露公开头文件,PRIVATE
隐藏私有头文件。
2.使用生成器表达式(Generator Expressions)
处理构建时与安装后的路径差异:
target_include_directories(mylib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> # 构建时
$<INSTALL_INTERFACE:include> # 安装后
)
3.避免重复路径
- 同一目标的相同路径只需设置一次,
PUBLIC
已包含PRIVATE
的效果。
4.路径顺序
- 多个
PRIVATE
/PUBLIC
/INTERFACE
块按添加顺序处理,先添加的路径优先级更高。 - 使用
BEFORE
参数可将路径插入到搜索列表的前面:
target_include_directories(my_app
BEFORE PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/override # 优先搜索此目录
)
5.目标必须先存在
target_include_directories
必须在目标(如add_executable
)创建后调用。
5.总结
命令 | 核心功能 | 作用域 | 使用场景 |
---|---|---|---|
include(<file>) |
导入并执行外部 CMake 脚本或模块 | 全局 | 模块化 CMake 代码、导入工具函数 |
include_guard() |
防止 CMake 脚本被重复包含 | 文件级 | 所有共享脚本的顶部 |
include_directories(...) |
设置全局头文件搜索路径 | 全局 | 简单项目、快速原型开发 |
target_include_directories() |
为特定目标设置头文件搜索路径(支持传递性依赖) | 目标级 | 库开发、模块化项目 |
相关链接
- CMake 官网 CMake - Upgrade Your Software Build System
- CMake 官方文档:CMake Tutorial — CMake 4.0.2 Documentation
- CMake 源码:https://github.com/Kitware/CMake
- CMake 源码:CMake · GitLab
- 中文版基础介绍: CMake 入门实战 | HaHack
- wiki: Home · Wiki · CMake / Community · GitLab