一个库在本地构建正常,被另一个包引用后出现找不到头文件或未定义符号。排查时发现库本身没问题,问题出在它对外声明依赖关系的方式上。CMake 用 PUBLIC、PRIVATE 和 INTERFACE 三个关键字描述依赖传播范围,写错会出现“自己能用、别人不能用”的现象。
这篇文章讨论这三个关键字各自的含义,以及怎么用一条命令验证依赖是否正确传播。
判断标准是依赖有没有出现在头文件里
最简单的判断方式:如果下游编译时需要在头文件里 include 这个依赖,那么它必须传播;如果只在本目标的实现文件里用到,就可以不传播。
1 | add_library(robot_task src/task.cpp) |
把应该在头文件里暴露的依赖写成 PRIVATE,是本机编译通过、下游报“找不到头文件”的最常见原因。反过来,把纯内部依赖写成 PUBLIC,会把实现细节泄漏给所有下游,增加耦合,也可能引入不必要的构建依赖。
头文件库用 INTERFACE
只有头文件的库自己不需要编译,但它给下游的要求是真实的:
1 | add_library(robot_headers INTERFACE) |
BUILD_INTERFACE 和 INSTALL_INTERFACE 的区别在于构建阶段与安装后使用不同的路径。只写绝对路径会在安装后失效,这是另一个常见的“本地正常、安装后失败”来源。
依赖必须能被下游解析
只在自己的 CMakeLists 里写 target_link_libraries 还不够。安装并导出后,下游通过 find_package 找到这个目标时,需要重新解析它的依赖。如果依赖没有一起被导出,配置阶段会报找不到目标。
1 | install(TARGETS robot_task EXPORT robot_task_targets ...) |
用 PUBLIC 或 INTERFACE 声明的依赖会进入导出信息,前提是这些依赖本身也能被下游找到。这就是为什么链接关系写错时,错误往往出现在下游的配置阶段,而不是本包的构建阶段。
验证要站在下游做
验证依赖传播最有效的方式是让另一个包真正用一次:
1 | # 在本包目录内构建成功,不能说明依赖传播正确 |
一个独立的最小消费工程能暴露三类问题:头文件目录没有传播、需要的依赖没有被导出、以及编译定义在安装后丢失。在自己的工程里编译,这些都不会暴露,因为源码目录仍然可见。
和交叉编译的关系
交叉编译场景下,这些范围问题会更加明显,因为下游找不到宿主机上的同名库。依赖传播范围的错误在交叉编译时往往从“偶尔失败”变成“必然失败”。
因此交叉编译的验证流程应当包含一个消费工程,而不是只把主库构建通过当作完成。这与交叉编译工具链文件的正确性是两件独立的事,需要分别验证。
参考资料
证据边界
本文讨论 CMake 依赖传播范围的语义与验证方式,不包含任何具体项目的构建配置。文中判断标准适用于常规 C++ 库;涉及生成代码、自定义命令或运行时插件的场景,依赖关系需要另行分析,不能直接套用。