cmake之旅(9)

张开发
2026/4/10 11:10:33 15 分钟阅读

分享文章

cmake之旅(9)
cmake之旅9cmake之旅9安装与导出1 什么是安装2 install 命令基础2.1 安装可执行文件2.2 安装库文件2.3 安装头文件2.4 GNUInstallDirs —— 标准化路径3 安装并导出 CMake 配置文件3.1 完整流程3.2 构建并安装3.3 别人如何使用4 关键概念详解4.1 BUILD_INTERFACE 和 INSTALL_INTERFACE4.2 EXPORT 和命名空间4.3 ALIAS 目标4.4 版本兼容5 本篇命令速查表6 总结与下一篇预告同系列文章cmake之旅(1):构建的过程cmake之旅(2):CMakeLists.txt 核心语法cmake之旅(3):多目录项目管理cmake之旅(4):静态库与动态库cmake之旅5):函数、宏与 .cmake 模块cmake之旅6查找和使用第三方库cmake之旅7编译选项与条件编译cmake之旅8Modern CMake 与 target 思维cmake之旅9安装与导出cmake之旅10自动化测试与 CTestcmake之旅9安装与导出上一篇我们掌握了 Modern CMake 的 target 思维。到目前为止我们写的所有库都是项目内使用的——编译完在 build 目录里只有当前项目能用。如果你写了一个很好用的库想分享给其他项目使用呢别人不可能把你的整个项目目录复制到自己的项目里。正确的做法是把你的库安装到系统中就像apt install那样然后别人通过find_package就能找到和使用你的库。这一篇我们就来学习 CMake 的安装install和导出export机制。1 什么是安装安装就是把编译好的产物库文件、头文件、可执行文件等复制到系统的标准目录中。在 Linux 系统上标准的安装目录通常是内容默认路径说明可执行文件/usr/local/bin/可直接在终端运行库文件/usr/local/lib/.a 和 .so 文件头文件/usr/local/include/公开头文件CMake 配置文件/usr/local/lib/cmake/包名/让 find_package 能找到安装后其他项目就可以像使用系统库一样使用你的库了。安装路径由CMAKE_INSTALL_PREFIX变量控制默认值是/usr/local。可以通过命令行修改cmake-DCMAKE_INSTALL_PREFIX/opt/mylibs..2 install 命令基础2.1 安装可执行文件add_executable(myapp main.cpp) # 安装可执行文件到 bin/ 目录 install(TARGETS myapp RUNTIME DESTINATION bin )执行make install后myapp会被复制到${CMAKE_INSTALL_PREFIX}/bin/myapp。2.2 安装库文件add_library(mylib SHARED src.cpp) install(TARGETS mylib ARCHIVE DESTINATION lib # 静态库 (.a) LIBRARY DESTINATION lib # 动态库 (.so) RUNTIME DESTINATION bin # Windows 上的 .dll )这里同时写了 ARCHIVE、LIBRARY、RUNTIME 三种CMake 会根据库的类型自动选择正确的目标。这是推荐的写法这样无论你构建的是静态库还是动态库安装路径都是正确的。2.3 安装头文件# 安装整个 include 目录下的头文件 install(DIRECTORY include/ DESTINATION include )注意include/后面的斜杠很重要include/有斜杠安装目录的内容结果是${PREFIX}/include/calc/add.hinclude没斜杠安装目录本身结果是${PREFIX}/include/include/calc/add.h2.4 GNUInstallDirs —— 标准化路径不同的 Linux 发行版对安装路径有不同的约定比如有些用lib64而不是lib。CMake 内置的GNUInstallDirs模块提供了标准化的路径变量include(GNUInstallDirs) install(TARGETS mylib ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} # 通常是 lib 或 lib64 LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} # 通常是 bin ) install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} # 通常是 include )建议始终使用 GNUInstallDirs 的变量而不是硬编码lib、bin、include。3 安装并导出 CMake 配置文件仅仅安装库文件和头文件是不够的。如果你希望别人能通过find_package(MyLib)找到你的库你还需要安装 CMake 配置文件MyLibConfig.cmake。这就是导出的含义——把你的 target 信息导出为 .cmake 文件让别人的 CMake 能识别你的库。3.1 完整流程我们用 Calculator 项目的 calc_lib 来演示完整的安装与导出流程。项目结构├── CMakeLists.txt ├── cmake │ └── CalcLibConfig.cmake.in# 配置文件模板├── include │ └── calc │ ├── add.h │ └── de.h └── src ├── add.cpp └── de.cpp注意这次没有 main.cpp因为这个项目的目的是构建一个给别人使用的库。CMakeLists.txtcmake_minimum_required(VERSION 3.14) project(CalcLib VERSION 1.0.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) include(GNUInstallDirs) # ---- 构建库 ---- add_library(calc_lib src/add.cpp src/de.cpp ) # 为库创建别名命名空间格式供 add_subdirectory 方式使用 add_library(CalcLib::calc ALIAS calc_lib) # 设置头文件路径 # BUILD_INTERFACE构建时使用的路径 # INSTALL_INTERFACE安装后使用的路径 target_include_directories(calc_lib PUBLIC $BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include $INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR} ) target_compile_features(calc_lib PUBLIC cxx_std_17) # ---- 安装库和头文件 ---- install(TARGETS calc_lib EXPORT CalcLibTargets # 导出目标集合 ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) # ---- 导出 CMake 配置文件 ---- # 安装导出的目标文件 install(EXPORT CalcLibTargets FILE CalcLibTargets.cmake NAMESPACE CalcLib:: # 为导入目标添加命名空间前缀 DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/CalcLib ) # 生成版本兼容文件 include(CMakePackageConfigHelpers) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/CalcLibConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion # 允许使用更新的版本 ) # 生成配置文件 configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CalcLibConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/CalcLibConfig.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/CalcLib ) # 安装配置文件和版本文件 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/CalcLibConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/CalcLibConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/CalcLib )cmake/CalcLibConfig.cmake.inPACKAGE_INIT include(${CMAKE_CURRENT_LIST_DIR}/CalcLibTargets.cmake) check_required_components(CalcLib)这个模板文件很短它做了两件事引入导出的目标文件以及检查必要组件是否存在。PACKAGE_INIT会被configure_package_config_file自动替换为初始化代码。3.2 构建并安装mkdirbuildcdbuild cmake-DCMAKE_INSTALL_PREFIX/tmp/calclib_install..makemakeinstall安装后的目录结构/tmp/calclib_install/ ├── include │ └── calc │ ├── add.h │ └── de.h └── lib ├── cmake │ └── CalcLib │ ├── CalcLibConfig.cmake │ ├── CalcLibConfigVersion.cmake │ ├── CalcLibTargets.cmake │ └── CalcLibTargets-release.cmake └── libcalc_lib.a3.3 别人如何使用现在其他项目就可以通过find_package来使用我们的库了cmake_minimum_required(VERSION 3.14) project(UserApp LANGUAGES CXX) # 查找 CalcLib find_package(CalcLib 1.0 REQUIRED) add_executable(app main.cpp) target_link_libraries(app PRIVATE CalcLib::calc)构建时指定安装路径cmake-DCMAKE_PREFIX_PATH/tmp/calclib_install..整个闭环打通了第六篇学了怎么用find_package找别人的库第五篇学了.cmake文件的写法现在我们学了怎么把自己的库导出成.cmake让别人找。4 关键概念详解4.1 BUILD_INTERFACE 和 INSTALL_INTERFACEtarget_include_directories(calc_lib PUBLIC $BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include $INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR} )这两个生成器表达式解决了一个实际问题构建时和安装后的头文件路径是不同的。构建时头文件在你的源码目录下比如/home/user/project/include。安装后头文件在系统目录下比如/usr/local/include。你不可能在安装后还让别人的 CMake 去找/home/user/project/include那个路径在别人的机器上根本不存在。BUILD_INTERFACE指定的路径只在构建时生效INSTALL_INTERFACE指定的路径只在安装后的配置文件中生效。CMake 会自动根据上下文选择正确的路径。4.2 EXPORT 和命名空间install(TARGETS calc_lib EXPORT CalcLibTargets ...) install(EXPORT CalcLibTargets NAMESPACE CalcLib:: ...)EXPORT CalcLibTargets把calc_lib这个目标注册到一个名为CalcLibTargets的导出集合中。随后install(EXPORT ...)把这个集合写入.cmake文件。NAMESPACE CalcLib::给导出的目标加上命名空间前缀。也就是说别人find_package后看到的目标名是CalcLib::calc_lib而不是裸的calc_lib。这样做的好处是避免名称冲突——万一别人的项目里也有一个叫calc_lib的目标呢4.3 ALIAS 目标add_library(CalcLib::calc ALIAS calc_lib)这行创建了一个别名目标。它的作用是如果有人通过add_subdirectory的方式直接引入你的项目而不是通过find_package安装后使用他也能用CalcLib::calc这个名字来链接保持接口一致。这样无论用哪种方式引入你的库使用者的 CMakeLists.txt 都可以统一写target_link_libraries(app PRIVATE CalcLib::calc)4.4 版本兼容write_basic_package_version_file( ... COMPATIBILITY AnyNewerVersion )COMPATIBILITY指定版本兼容策略策略含义AnyNewerVersion任何更新的版本都兼容SameMajorVersion主版本号相同即兼容如 1.x 兼容 1.ySameMinorVersion主版本和次版本号相同即兼容ExactVersion必须精确匹配当别人写find_package(CalcLib 1.0 REQUIRED)时CMake 会检查安装的版本是否满足这个兼容策略。5 本篇命令速查表命令作用示例install(TARGETS ...)安装目标文件见第 2 节install(DIRECTORY ...)安装目录install(DIRECTORY include/ DESTINATION include)install(FILES ...)安装指定文件install(FILES config.cmake DESTINATION lib/cmake)install(EXPORT ...)安装导出文件见第 3 节write_basic_package_version_file生成版本兼容文件见第 3 节configure_package_config_file生成包配置文件见第 3 节生成器表达式表达式含义$BUILD_INTERFACE:path仅构建时有效的路径$INSTALL_INTERFACE:path仅安装后有效的路径6 总结与下一篇预告这一篇我们学会了用install安装库文件和头文件用install(EXPORT)和configure_package_config_file导出 CMake 配置文件让别人可以通过find_package找到并使用我们的库。这和第六篇形成了完整的闭环。到此为止我们的库已经可以构建、安装、给别人使用了。但一个高质量的库不仅要能用还要可靠。怎么确保你的库在修改代码后功能还是正确的答案是自动化测试。下一篇——cmake之旅10自动化测试与 CTest我们来学习如何在 CMake 中集成单元测试。

更多文章