自己包里编译通过,被别人引用就报找不到头文件——这种情况我遇到过好几次,每次第一反应都是怀疑对方环境有问题,最后发现是 PRIVATE 和 PUBLIC 写反了。
CMake 用这三个关键字描述依赖传播范围。写错的结果就是“自己能用、别人不能用”。
判断标准是依赖有没有进头文件
最简单的判断:下游编译时需不需要在头文件里 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 | # 在本包目录里构建成功,说明不了传播是对的 |
一个独立的最小消费工程能暴露三类问题:头文件目录没传播、需要的依赖没被导出、编译定义安装后丢了。在自己工程里编译这些都不会暴露,因为源码目录一直可见。
交叉编译下更明显
交叉编译场景里这些问题会更突出,因为下游找不到宿主机上的同名库。依赖传播范围写错,在交叉编译时往往从“偶尔失败”变成“必然失败”。
所以交叉编译的验证流程应该包含一个消费工程,而不是主库构建通过就算完。这跟工具链文件对不对是两件独立的事,得分别验。
参考资料
证据边界:本文不含任何具体项目的构建配置。上面的判断标准适用于常规 C++ 库;涉及生成代码、自定义命令或者运行时插件的场景,依赖关系要另行分析,不能直接套。