CMake库打包以及支持find_package(二)

在线体验各类最新模型,更有模型 免费Token 额度领取!
立即体验
简介: CMake库打包以及支持find_package(二)

CMake导出库


经过上述步骤,我们已经安装了我们库的所有东西,现在其他用户可以通过include_directoriesadd_libraries以及制定链接的目标等相应操作来使用我们的库,但是我们希望能够像OpenCV一样,让我们的库的CMake整合到别人的项目中去。

为此,CMake提供了目标导出的功能。导出一个目标能够让我们重复利用一个CMake工程,比如一些变量等等,就像用户自己定义的一样,最常见的就是OpenCV和NDK这类的工程。

为了使用导出功能,需要创建一个my_library.cmake文件,其中包含了所有编译和安装目标的引用,用户只需要包含这个文件就可以使用前面编译和安装的库。为了能够导出my_library库,我们需要做两件事:

  • 首先,对于每个目标,需要指明其是否导出。这个可以通过在install(TARGET)命令中添加EXPORT my_library选项,像下面这样:
  • install(TARGETS my_library EXPORT my_library DESTINATION "${lib_dest}")
  • 其次,导出的目标也需要安装,这个可以通过在根目录下的CMakeLists.txt中的install(EXPORT)命令完成。因为我们的编译类型和config_impl.hpp的位置以及库的目标位置有关,二者会被安装到${lib_dest},因此,安装命令如下:
  • install(EXPORT my_library DESTINATION '${lib_dest}')

现在还有一个小问题:我们的目标库设定了target_include_directories(),这个设置会将我们要构建的目标(add_library或者add_executable所指定的生成目标)所使用的头文件目录传递给目标,并且安装时也是依据这个来安装目标对象的头文件,这就导致构建和安装的时候,必须使用同一个include目录。而且这个目录是不能随意更改的,否则在构建的时候会出现问题。


这里有个额外的知识,我们知道target_include_directories()指定了对象的include目录,当这个target T 被其他的target通过target_link_libraries()链接的时候,T中通过target_include_directories()指定的PUBLICINTERFACE目录,会被自动导入到其他的target的include_directories中。这也是target_include_directories()在项目的多模块引用时的一个用法。


CMake有一个特性可以支持修复上述的问题,就是生成器表达式,这个特性可以允许设定目标对象在构建和安装时,使用不同的include目录,我们需要将target_include_directories()调用改为如下的格式:


target_include_directories(my_library PUBLIC
        $<BUILD_INTERFACE:${MY_LIBRARY_SOURCE_DIR}/include> # for headers when building
        $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}> # for config_impl.hpp when building
        $<INSTALL_INTERFACE:${include_dest}> # for client in install mode
        $<INSTALL_INTERFACE:${lib_dest}> # for config_impl.hpp in install mode)


这样我们就有了一个my_library.cmake,当需要用到my_library库的时候,只需要通过include(/path/to/installation/my_library-1.0/Debug/my_library.cmake)来直接引用,而不比再搞一大堆类似lib的路径,include的路径等等操作了。

但是现在还不是最终形式,我们前面说了,要搞成类似OpenCV那种支持自动find的形式,接着就是最后的支持包搜索了。


支持find_package


CMake支持find_package(),相信大家在Linux上面用OpenCV,很多都是直接用这条命令。

当我们用find_package(my_library ...)这条命令时,它去${CMAKE_INSTALL_PREFIX}/lib目录下一个名为my_library*的文件夹中自动去寻找一个类似my_library-config.cmake的文件,而我们的安装命名就是符合这个规则的,lib/my_library-[major].[minor] - ${main_lib_dest}。所以现在我们只需要提供my_library-config.cmake文件。

这个文件的内容是能够被find_package()直接调用的脚本,通常包含了定义目标的代码,而这些代码我们已经通过install(EXPORT)命令生成在my_library.cmake文件中了,因此我们只需要在my_library-config.cmake文件中include()这个文件。包含的时候也要匹配相应的版本号和编译类型


# my_library-config.cmake - package configuration file
get_filename_component(SELF_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
include(${SELF_DIR}/${CMAKE_BUILD_TYPE}/my_library.cmake)


这个文件同样能够存储在我们的库中,必须记住在安装的时候顺便将其安装了:


install(FILES my_library-config.cmake DESTINATION ${main_lib_dest})
install(EXPORT ...)


现在,用户只需要在自己的CMake项目中调用find_package(my_library REQUIRED),这个库就会被自动搜索和找到(如果该库的${CMAKE_BUILD_TYPE}类型已经被安装了),并且导出所有的对象,然后方便用户直接链接:target_link_libraries(client_target PUBLIC my_library),能够直接链接到正确的版本和构建类型。

REQUIRED并非必须,但是在引用目标的时候就必须附加相应的变量。


版本控制


find_package()同样支持版本控制,你可以传入版本号作为第二个参数。

find_package()的版本控制是通过一个类似名为my_library-config-version.cmake文件完成的,和my_library-config.cmake类似,你需要在库中提供并安装它。

这个版本控制文件接受${PACKAGE_FIND_VERSION_MAJOR/MINOR}格式的版本号,并设置相应的合适版本号变量${PACKAGE_VERSION_EXACT/COMPATIBLE/UNSUITABLE},以及完整的版本号${PACKAGE_VERSION}。但是仅仅设置版本号相关的变量还没有解决一个问题:到底哪个版本的库将会被安装。为此,我们需要在安装之前通过引用根目录的CMakeLists.txt中的版本号相关的变量来进行安装的配置。

这里有一个简单的脚本,需要一个指定的大版本号以及必须大于等于的小版本号:


# my_library-config-version.cmake - checks version: major must match, minor must be less than or equal
set(PACKAGE_VERSION @MY_LIBRARY_VERSION@)
if("${PACKAGE_FIND_VERSION_MAJOR}" EQUAL "@MY_LIBRARY_VERSION_MAJOR@")
    if ("${PACKAGE_FIND_VERSION_MINOR}" EQUAL "@MY_LIBRARY_VERSION_MINOR@")
        set(PACKAGE_VERSION_EXACT TRUE)
    elseif("${PACKAGE_FIND_VERSION_MINOR}" LESS "@MY_LIBRARY_VERSION_MINOR@")
        set(PACKAGE_VERSION_COMPATIBLE TRUE)
    else()
        set(PACKAGE_VERSION_UNSUITABLE TRUE)
    endif()
else()
    set(PACKAGE_VERSION_UNSUITABLE TRUE)
endif()


可以在根目录的CMakeLists.txt中配置相应的版本(通过替换@中的版本变量为相应的正确版本号)和完成安装。


configure_file(my_library-config-version.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/my_library-config-version.cmake @ONLY)
install(FILES my_library-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/my_library-config-version.cmake DESTINATION ${main_lib_dest})
install(EXPORT ...)


这里@NOLY主要是为了不符合CMake的变量命名方式。

现在调用find_package(my_library 1.0 REQUIRED)这种形式,可以寻找1.0或者相兼容版本(如果你设置了兼容的版本号)的库。


总结


总结来看,为了在CMake中支持库的安装和find_package(),我们需要:

  • 改变库目标的target_include_directories(),使用$<BUILD_INTERFACE:>$<INSTALL_INTERFACE:>生成器表达式来设置正确的include目录。在安装模式下,就是把库的头文件将会被安装的位置。(看下一条)
  • 通过install(FILES)安装头文件到include/my_library-[major].[minor]中。
  • 通过install(FILES)安装相应的配置头文件(或者其他依赖于构建类型(build type)的头文件)到lib/my_library-[major].[minor]/${CMAKE_BUILD_TYPE}/中。
  • 通过install(TARGET target EXPORT my_library ...)安装库文件到lib/my_library-[major].[minor]/${CMAKE_BUILD_TYPE}/中,这条命令也将目标加入导出集合中。
  • 定义一个命名为my_library-config.cmake的文件,其中包含了相应的my_library.cmake文件。还需要定义一个my_library-config-version.cmake.in配置文件,和上述一样,用于版本兼容性检查和控制。
  • 通过configure_file(...)来配置正确的版本安装的文件,然后通过install(FILES)安装相应版本控制文件和my_library-config.cmake文件到lib/my_library-[major].[minor]/中。
  • 通过install(EXPORT)安装导出集合中的的库文件到lib/my_library-[major].[minor]/${CMAKE_BUILD_TYPE}/

现在,用户只需要通过如下方式来调用:


find_package(my_library 1.0 REQUIRED)  
target_link_libraries(client_target PUBLIC my_library)


这样就能够自动地寻找合适版本的库进行链接。

目录
相关文章
|
存储 Shell iOS开发
CMake中文手册_target_link_libraries(3.26)
CMake中文手册_target_link_libraries(3.26)
2047 0
|
存储 设计模式 安全
C++一分钟之-并发编程基础:线程与std::thread
【6月更文挑战第26天】C++11的`std::thread`简化了多线程编程,允许并发执行任务以提升效率。文中介绍了创建线程的基本方法,包括使用函数和lambda表达式,并强调了数据竞争、线程生命周期管理及异常安全等关键问题。通过示例展示了如何用互斥锁避免数据竞争,还提及了线程属性定制、线程局部存储和同步工具。理解并发编程的挑战与解决方案是提升程序性能的关键。
665 3
|
存储 自然语言处理 关系型数据库
MySQL 自定义变量并声明字符编码
MySQL 自定义变量并声明字符编码
579 1
|
存储 Linux 数据库
云计算的体系结构
云计算的体系结构由5部分组成,分别为应用层,平台层,资源层,用户访问层和管理层,云计算的本质是通过网络提供服务,所以其体系结构以服务为核心。 如下图: 1,资源层 资源池层是指基础架构屋面的云计算服务,这些服务可以提供虚拟化的资源,从而隐藏物理资源的复杂性。
5000 0
|
安全 Java Unix
【C++ 包裹类 std::thread】探索C++11 std::thread:如何使用它来创建、销毁和管理线程
【C++ 包裹类 std::thread】探索C++11 std::thread:如何使用它来创建、销毁和管理线程
1013 0
|
算法 Unix Linux
【Linux 库管理工具】深入解析pkg-config与CMake的集成与应用
【Linux 库管理工具】深入解析pkg-config与CMake的集成与应用
1652 0
|
Ubuntu Shell API
Ubuntu 64系统编译android arm64-v8a 的openssl静态库libssl.a和libcrypto.a
Ubuntu 64系统编译android arm64-v8a 的openssl静态库libssl.a和libcrypto.a
|
前端开发 JavaScript Java
谷粒商城笔记+踩坑(3)——商品服务-三级分类、网关跨域
商品服务-三级分类增删改查、跨域问题、逻辑删除
|
编解码 开发框架
【Qt 学习笔记】Qt窗口 | Qt窗口介绍 | QMainwindow类及各组件介绍
【Qt 学习笔记】Qt窗口 | Qt窗口介绍 | QMainwindow类及各组件介绍
1119 3