第27章 导出与导入:export、install(EXPORT ...)、find_package消费自己的库
好,咱们今天聊一个实战性很强的话题——怎么把自己写的库“交出去”,让别人用find_package找到它。
很多新手写完一个库,直接把头文件和.so/.a扔给同事,然后丢一句“你链接一下就行”。结果对方折腾半天,不是找不到头文件,就是链接报错。嗯,这种场景我见过太多次了。
CMake提供了一套完整的“导出与导入”机制。说白了,就是让你的库变得像OpenCV、Boost那样,别人一句find_package就能用。
27.1 为什么需要导出?
先想一个问题:你写了一个数学库叫MathLib,里面有加法、乘法。同事小明想用,他需要知道什么?
- 头文件在哪?
- 库文件在哪?
- 链接哪个库?Debug还是Release?
- 有没有依赖别的库?
如果你手动告诉他,每次更新都得重新说一遍。累不累?
导出机制就是把这些信息打包成一个“配置文件”。CMake通过这个文件,自动帮你搞定所有路径和链接参数。
27.2 两种导出场景
我个人习惯把导出分成两种情况:
| 场景 | 说明 | 常用命令 |
|---|---|---|
| 构建树内导出 | 在源码构建目录里直接用,不安装 | export(TARGETS ...) |
| 安装后导出 | 把库安装到系统路径后,供其他项目使用 | install(EXPORT ...) |
构建树内导出适合开发阶段快速测试。安装后导出才是生产环境的标准做法。
27.3 实战:写一个可导出的库
咱们直接上代码。假设我有一个简单的加法库:
# MathLib/CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(MathLib VERSION 1.0.0)
add_library(MathLib STATIC src/add.cpp)
target_include_directories(MathLib PUBLIC include)
# 第一步:生成导出头文件
include(GenerateExportHeader)
generate_export_header(MathLib)
# 第二步:安装头文件和库
install(TARGETS MathLib
EXPORT MathLibTargets
ARCHIVE DESTINATION lib
PUBLIC_HEADERS DESTINATION include
)
# 第三步:安装CMake配置文件
install(EXPORT MathLibTargets
FILE MathLibTargets.cmake
NAMESPACE MathLib::
DESTINATION lib/cmake/MathLib
)
# 第四步:生成并安装Config文件
include(CMakePackageConfigHelpers)
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/MathLibConfig.cmake
INSTALL_DESTINATION lib/cmake/MathLib
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/MathLibConfig.cmake
DESTINATION lib/cmake/MathLib
)
这里有个细节我特别想强调:NAMESPACE。加上MathLib::这个命名空间后,别人用的时候就是MathLib::MathLib,一看就知道是哪个库的。我曾经见过一个项目,三个库都叫core,链接的时候全乱了……
27.4 Config.cmake.in 模板文件
这个文件是“说明书”的模板。CMake会用实际路径替换里面的变量:
# MathLib/Config.cmake.in
@PACKAGE_INIT@
set_and_check(MathLib_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
set_and_check(MathLib_LIBRARY_DIR "@PACKAGE_LIB_INSTALL_DIR@")
include("${CMAKE_CURRENT_LIST_DIR}/MathLibTargets.cmake")
check_required_components(MathLib)
注意那个@PACKAGE_INIT@,它是CMake自动替换的宏,用来处理路径计算。我刚开始写的时候忘了加这行,结果路径全是绝对路径,换个机器就炸了。
27.5 消费者怎么用?
现在小明想用我的库,他只需要:
# 小明的项目/CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(MyApp)
find_package(MathLib REQUIRED)
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE MathLib::MathLib)
就这三行。头文件路径、库路径、链接参数,全部自动搞定。
-DCMAKE_PREFIX_PATH=/path/to/MathLib/build 指定构建目录。这样不用安装也能测试。
27.6 避坑指南
我这些年踩过的坑,列几个典型的:
- 忘记PUBLIC头文件:
target_include_directories一定要用PUBLIC,否则导出后别人找不到头文件。 - 命名空间不一致:导出时用了
MathLib::,消费时也要用MathLib::MathLib。少一个冒号都不行。 - 路径硬编码:永远不要在Config.cmake.in里写死路径。用
@PACKAGE_INIT@和set_and_check。 - 忘记安装依赖:如果你的库依赖了别的库,记得在Config.cmake.in里也
find_dependency。
27.7 知识体系图
下面这张图总结了整个导出导入的流程:
27.8 总结
导出与导入,说白了就是让你的库拥有“自描述能力”。别人不需要知道你的库是怎么组织的,只需要find_package然后target_link_libraries。
我个人建议:从写第一个库开始就加上导出配置。哪怕现在只有你自己用,养成习惯后,以后团队协作、开源发布都会轻松很多。
嗯,这一章的内容就到这里。记住那个流程图,理解清楚“作者导出”和“消费者导入”两个角色,你就掌握了CMake库分发的核心。