CMake include_directories()使用指南
问题
请帮我详细解释一下CMakeLists.txt
的include_directories()
函数,比如我应该如何写${Boost_INCLUDE_DIRS}
这一类include dirs
,它们大小写敏感吗,我怎么知道Include
了之后都Include了什么,比如Boost是不是还有Component,那这个Boost_include_dirs
会包含component
的头文件吗
include_directories(include${Boost_INCLUDE_DIRS}${catkin_INCLUDE_DIRS}${GAZEBO_INCLUDE_DIRS}
)
回答
一、include_directories()
是什么、作用范围、常见写法
- 作用:把给定目录加到当前目录及其子目录目标的“头文件搜索路径”中(相当于编译器的 -I 或 /I 参数),这样源代码里的
#include
就能从这些目录里找到头文件。 - 作用范围:
- 当前
CMakeLists.txt
调用后,默认影响当前目录与其子目录中将要定义的目标(target)。对已经定义过的目标,不一定会生效。 - 从
CMake 2.8.12
起,推荐使用target_include_directories()
精确控制作用范围和可见性(PRIVATE/INTERFACE/PUBLIC)。include_directories() 属于“目录属性”,容易影响过度。
- 当前
- 常见写法:
- 绝对路径、相对路径(相对当前
CMake
源目录)、或生成的变量(如${Boost_INCLUDE_DIRS}
)。 - 示例(你提供的)在目录级别加了四类
include
路径:本工程头文件、Boost、catkin、Gazebo。
- 绝对路径、相对路径(相对当前
二、${Boost_INCLUDE_DIRS}
该怎么写、是否区分大小写
- 变量名大小写敏感:在
CMake
中,变量名区分大小写;${Boost_INCLUDE_DIRS}
和${boost_include_dirs}
是两个不同变量。 - 变量内容来自
find_package(Boost ...)
:- 通常你会先写:
find_package(Boost REQUIRED COMPONENTS filesystem system ...)
- 找到后,模块会设置:
Boost_FOUND
Boost_INCLUDE_DIRS
(包含Boost
根头文件路径,且通常已包含所请求组件的头文件路径——见下文)Boost_LIBRARIES
、Boost_<component>_LIBRARY
等
- 通常你会先写:
- 正确写法:
- 先调用
find_package(Boost ...)
,再在include_directories(...)
中使用${Boost_INCLUDE_DIRS}
。 - 如果用的是现代
CMake
,推荐用目标形式:target_link_libraries(myexe PRIVATE Boost::filesystem Boost::system)并配合
target_include_directories或依赖目标自动传递包含路径(
INTERFACE包含路径会自动随
target` 传递)。
- 先调用
三、Boost 是否有 Component、${Boost_INCLUDE_DIRS} 是否包含组件头文件
- 是的,
Boost
按组件查找:filesystem
、system
、regex
、thread
等。 - 头文件位置:
- 多数 Boost 组件是头文件为主(header-only),集中在同一个包含根目录,如
/usr/include
或 C:/local/boost_X_Y_Z。组件的头文件基本都在同一套 include 根中(boost/…)。 - 对于需要编译的组件(如 filesystem、thread),除了头文件(仍在同一 include 根)外,还有库文件需要链接。
- 多数 Boost 组件是头文件为主(header-only),集中在同一个包含根目录,如
- ${Boost_INCLUDE_DIRS} 的内容:
- 通常是 Boost 的“公共 include 根目录”(例如 /usr/include 或 /usr/local/include 或 自定义安装路径/include),它已经足够让你 #include <boost/…> 访问任意组件头文件。
- 也就是说,是否指定 COMPONENTS 并不会改变 include 路径的数量,通常仍然是一个或少数几个 include 根目录;组件区分更多体现在库的链接阶段(Boost_LIBRARIES/Boost::)。
- 例外情况:
- 非常规安装或多版本共存时,可能出现额外的 include 目录,但本质仍是把包含根放进来,能覆盖所有组件头。
四、如何“知道 Include 了什么”
- 从
CMake
角度:message(STATUS "Boost include dirs: ${Boost_INCLUDE_DIRS}")
打印查看。cmake --trace-expand
或者调高 CMake 输出,查看变量展开。
- 从编译器调用角度:
- 开启
VERBOSE=1
或CMAKE_VERBOSE_MAKEFILE=ON
,观察实际编译命令行里的 -I 路径。 Ninja/Make
会把-I...
全部打印出来。
- 开启
- 从 IDE/生成系统角度:
- 生成
compile_commands.json
(set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
),查看每个编译单元的include
路径。
- 生成
- 运行期检查:
- 写个小源文件,
#include <boost/version.hpp>
并打印BOOST_LIB_VERSION
来检验是否找到了预期的头文件版本。
- 写个小源文件,
五、与 catkin、GAZEBO 的变量
${catkin_INCLUDE_DIRS}
:来自find_package(catkin REQUIRED ...)
和catkin_package()
,包含catkin
工作空间里各依赖包导出的头文件路径,通常已聚合了你在catkin_package(CATKIN_DEPENDS ...)
指定的依赖的include
。${GAZEBO_INCLUDE_DIRS}
:来自find_package(gazebo ...)
或Gazebo
的cmake
配置,包含Gazebo
及其依赖的头文件路径。- 同样大小写敏感;这些变量通常也是“聚合后的
include
路径集合”。
六、现代写法建议(更可控)
- 少用目录级 include_directories,改为针对目标:
find_package(Boost REQUIRED COMPONENTS filesystem system)
add_library(mylib src/...)
target_include_directories(mylibPRIVATE${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_link_libraries(mylibPRIVATEBoost::filesystemBoost::system${catkin_LIBRARIES}${GAZEBO_LIBRARIES}
)
- 若使用目标别名(如
catkin
提供的目标、Gazebo
提供的导入目标),其INTERFACE_INCLUDE_DIRECTORIES
会自动传递无需手动加${xxx_INCLUDE_DIRS}
。如果没有提供目标,则继续沿用变量。 - 好处:可见性明确(
PRIVATE/INTERFACE/PUBLIC
),避免对所有子目录“全局污染”。
七、常见坑与排查
- 变量未定义或为空:确认
find_package
成功,检查Boost_FOUND
、catkin_FOUND
等。 - 多版本冲突:系统自带和自编译
Boost
并存时,确保CMAKE_PREFIX_PATH
或Boost_ROOT
指向你想要的版本,检查编译命令行的-I
顺序(越前优先)。 include
顺序问题:多个路径下有同名头文件时,会以命令行顺序优先。必要时把本工程的 include 放在最前。Windows
上用vcpkg/Conan
:优先用导入目标(Boost::…)避免路径细节。
总结回答你的核心问题
include_directories()
会把给定目录加入当前及子目录目标的头文件搜索路径。写法就是列出目录或变量展开的目录。${Boost_INCLUDE_DIRS}
要在find_package(Boost ...)
成功后使用;变量名大小写敏感。Boost
的组件主要影响链接库集合,头文件通常都在同一个包含根目录;因此${Boost_INCLUDE_DIRS}
一般已经覆盖所有组件的头文件,不需要为每个组件单独添加include
路径。- 想知道实际包含了哪些目录,可以打印变量、开启编译详细输出或查看
compile_commands.json
。