当前位置: 首页 > news >正文

CMake Error at CMakeLists.txt (find_package)幕后真凶

在这里插入图片描述

竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生~
个人主页: rainInSunny  |  个人专栏: C++那些事儿、 Learn OpenGL In Qt

文章目录

  • 写在前面
  • find_package报错解决
    • Module模式
    • Config模式
  • find_package()用法
    • Module模式
    • Config模式

写在前面

  本文从CMake中find_package()报错入手,首先给出了如何解决这类报错,然后深入探讨了find_package()的用法,揭示了find_package()背后帮我们做的事情,阐明了find_package()Module模式和Config模式的区别,同时针对在这两种模式下如何为自己写的三方库适配find_package()给出了详细说明。

find_package报错解决

  find_package报错描述的是依赖的三库文件没有找到,要解决这个问题也很简单,告诉find_package()要找的三方库在哪就行。先分析出错的原因:

  1. 可能是没有安装find_package寻找的三方库。
  2. 安装了三方库,但是是没设置路径或者路径设置错误。

  基本就是上面两种原因,那破案就简单了。针对第一种情况没什么好说的,先安装三方库再说。针对第二种情况,由于find_package支持Module模式和Config模式,下面举例分析。

Module模式

  以FontConfig为例,编译安装好后目录结构如下。一般在share/cmake目录能否找到提供find_package()支持的构建配置文件,Find<package>.cmake,这里名称为FindFontConfig.cmake。如果报错找的三方库安装目录有这样名称的文件,那么只需要将这个文件的路径,例子中也就是FindFontConfig.cmake路径添加到CMAKE_MODULE_PATH中,就像这样set(CMAKE_MODULE_PATH path/to/fontconfig;${CMAKE_MODULE_PATH}")

在这里插入图片描述
在这里插入图片描述

Config模式

  以Qt5为例,官方的安装目录结构如下。一般在share/cmake目录能否找到提供find_package()支持的构建配置文件,<package>Config.cmake,这里名称为Qt5Config.cmake,注意也可能通过-连接,即<package>-config.cmake。如果找到三方库中有这里文件,那么只需要将该路径添设置到package_DIR变量中,就像这样set(Qt5_DIR path/to/Qt5)

在这里插入图片描述
在这里插入图片描述

  核心思路就是通过三方库提供的配置文件名称来确定是Module模式还是Config模式,然后添加到CMake对应的预设变量中,帮助find_package找到三方库的构建配置即可。如果Module模式和Config模式同时存在,CMake会优先使用Module模式。

find_package()用法

  上面提到了find_package()有Module和Config两种模式,虽然问题解决了,但是如果自己开发的三方库需要给使用者提供这样便捷的能力,就需要更加理解find_package(),下面就仔细聊聊find_package(),其实本质目的就是帮助工程找到依赖三方库的头文件、动态库/静态库文件,因为在编译链接过程中需要这些文件。

Module模式

  假设你写出了一个震惊世界的消息库ZeroMQ,显然你希望大家能够很容易的在CMake中使用,就像这样find_package(ZeroMQ REQUIRED),为此你必须提供一个名称为Find<package>.cmake的文件来支持find_package(),这里文件的名称为FindZeroMQ.cmake。在其它三方库安装目录看到类似Find<package>.cmake,可以知道这个三方库支持find_package()的Module模式。接下来让我们一步步准备好FindZeroMQ.cmake。

  1. 首先FindZeroMQ.cmake中需要检查ZeroMQ_ROOT变量是否设置。此变量可用于ZeroMQ库的检测,并引导到自定义安装目录。用户可能设置了ZeroMQ_ROOT作为环境变量,类似<package>_ROOT一般用于指向package的安装路径,该路径下包含了三方库的头文件、库文件等。
if(NOT ZeroMQ_ROOT)set(ZeroMQ_ROOT "$ENV{ZeroMQ_ROOT}") //如果用户没有定义ZeroMQ_ROOT,则设置为环境变量中的ZeroMQ_ROOT
endif()
  1. 一般来说,当依赖一个三方库时最好显示指定三方库安装路径。如果ZeroMQ_ROOT有定义,则赋值给_ZeroMQ_ROOT。如果上面既没有定义ZeroMQ_ROOT,又没有在环境变量中设置ZeroMQ_ROOT的值,则尝试通过find_path去找三方库的典型头文件,并指定这个路径为_ZeroMQ_ROOT的值。_ZeroMQ_ROOT是最后实际查找ZeroMQ库的路径。通常会将三方库的路径添加到在${CMAKE_MODULE_PATH}中
if(NOT ZeroMQ_ROOT)find_path(_ZeroMQ_ROOT NAMES include/zmq.h)
else()set(_ZeroMQ_ROOT "${ZeroMQ_ROOT}")
endif()find_path(ZeroMQ_INCLUDE_DIRS NAMES zmq.h HINTS ${_ZeroMQ_ROOT}/include) //ZeroMQ_INCLUDE_DIRS设置为头文件路径
  1. 如果成功找到头文件,则将ZeroMQ_INCLUDE_DIRS设置为其位置。然后继续通过使用字符串操作和正则表达式,寻找相应版本的ZeroMQ库,如果没有多版本区分的需求,不需要这个步骤,后续说明将省略版本号区分。
set(_ZeroMQ_H ${ZeroMQ_INCLUDE_DIRS}/zmq.h)function(_zmqver_EXTRACT _ZeroMQ_VER_COMPONENT _ZeroMQ_VER_OUTPUT)
set(CMAKE_MATCH_1 "0")
set(_ZeroMQ_expr "^[ \\t]*#define[ \\t]+${_ZeroMQ_VER_COMPONENT}[ \\t]+([0-9]+)$")
file(STRINGS "${_ZeroMQ_H}" _ZeroMQ_ver REGEX "${_ZeroMQ_expr}")
string(REGEX MATCH "${_ZeroMQ_expr}" ZeroMQ_ver "${_ZeroMQ_ver}")
set(${_ZeroMQ_VER_OUTPUT} "${CMAKE_MATCH_1}" PARENT_SCOPE)
endfunction()_zmqver_EXTRACT("ZMQ_VERSION_MAJOR" ZeroMQ_VERSION_MAJOR)
_zmqver_EXTRACT("ZMQ_VERSION_MINOR" ZeroMQ_VERSION_MINOR)
_zmqver_EXTRACT("ZMQ_VERSION_PATCH" ZeroMQ_VERSION_PATCH)
  1. 接着使用find_library命令搜索ZeroMQ库。因为库的命名有所不同,这里我们需要区分Unix的平台和Windows平台。
if(NOT ${CMAKE_C_PLATFORM_ID} STREQUAL "Windows")find_library(ZeroMQ_LIBRARIESNAMESlibzmqHINTS${_ZeroMQ_ROOT}/lib${_ZeroMQ_ROOT}/lib/x86_64-linux-gnu)
else()find_library(ZeroMQ_LIBRARIESNAMESzmqHINTS${_ZeroMQ_ROOT}/lib)
endif()//如果需要区分release和debug
if(NOT ${CMAKE_C_PLATFORM_ID} STREQUAL "Windows")find_library(ZeroMQ_LIBRARY_RELEASENAMESlibzmqHINTS${_ZeroMQ_ROOT}/lib${_ZeroMQ_ROOT}/lib/x86_64-linux-gnu)find_library(ZeroMQ_LIBRARY_DEBUGNAMESlibzmqdHINTS${_ZeroMQ_ROOT}/lib${_ZeroMQ_ROOT}/lib/x86_64-linux-gnu)
else()find_library(ZeroMQ_LIBRARY_RELEASENAMESzmqHINTS${_ZeroMQ_ROOT}/lib)find_library(ZeroMQ_LIBRARY_DEBUGNAMESzmqdHINTS${_ZeroMQ_ROOT}/lib)
endif()
  1. 最后包含了标准FindPackageHandleStandardArgs.cmake,并调用相应的CMake命令。如果找到所有需要的变量,这里是ZeroMQ_INCLUDE_DIRSZeroMQ_LIBRARIES,则将ZeroMQ_FOUND变量设置为TRUE。完成这些步骤后,用户就能通过find_package(ZeroMQ REQUIRED)来使用ZeroMQ库,通过ZeroMQ_FOUND判断find_package是否成功,如果成功${ZeroMQ_INCLUDE_DIRS}${ZeroMQ_LIBRARIES}变量中就保存了ZeroMQ库的头文件路径和库文件路径供用户在target_include_directories和target_link_libraries中使用。
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(ZeroMQFOUND_VARZeroMQ_FOUNDREQUIRED_VARSZeroMQ_INCLUDE_DIRSZeroMQ_LIBRARIES)//如果需要区分release和debug
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(ZeroMQFOUND_VARZeroMQ_FOUNDREQUIRED_VARSZeroMQ_INCLUDE_DIRSZeroMQ_LIBRARY_RELEASEZeroMQ_LIBRARY_DEBUG)

Config模式

  Config模式相对于Module模式更为复杂些,需要配合目标安装过程,如果使用的三方库是二进制文件并不是源码编译的,建议通过Module模式为三方库提供find_package支持。这里假设我们通过自己编写的源码编译出了message这样一个target,我们需要通过Config模式为message提供find_package支持。接下来一步步实现这个目标

  1. 在安装目标过程中添加EXPORT关键字,这样CMake将为目标生成一个导出的目标文件。这里的${INSTALL_LIBDIR}和${INSTALL_BINDIR}分别为库文件和可执行文件安装目录。
install(TARGETSmessageEXPORTmessageTargetsARCHIVEDESTINATION ${INSTALL_LIBDIR}COMPONENT libRUNTIMEDESTINATION ${INSTALL_BINDIR}COMPONENT binLIBRARYDESTINATION ${INSTALL_LIBDIR}COMPONENT libPUBLIC_HEADERDESTINATION ${INSTALL_INCLUDEDIR}/messageCOMPONENT dev)
  1. 自动生成的导出目标文件称为messageTargets.cmake,需要显式地指定它的安装规则。这里的${INSTALL_CMAKEDIR}是CMake配置文件路径,一般设置为库安装路径下的share目录。
install(EXPORTmessageTargetsNAMESPACE"message::"DESTINATION${INSTALL_CMAKEDIR}COMPONENTdev)
  1. 需要生成正确的CMake配置文件,这些将确保下游项目能够找到消息库导出的目标。为此需要包括CMakePackageConfigHelpers.cmake标准模块,然后用模块提供的函数生成版本配置文件messageConfigVersion.cmake和配置文件messageConfig.cmake。注意这里的messageConfig.cmake是CMake通过模版文件messageConfig.cmake.in生成的,这个模版文件需要在message库的源码中提供,这里目录为${PROJECT_SOURCE_DIR}/cmake/messageConfig.cmake.in,模版的内容很简单,如下。
include(CMakePackageConfigHelpers)
write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/messageConfigVersion.cmakeVERSION ${PROJECT_VERSION}COMPATIBILITY SameMajorVersion)configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/messageConfig.cmake.in${CMAKE_CURRENT_BINARY_DIR}/messageConfig.cmakeINSTALL_DESTINATION ${INSTALL_CMAKEDIR})
// messageConfig.cmake.in
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/messageTargets.cmake")
  1. 最后安装配置文件。完成后用户在CMake中设定<package>_DIR变量指向这两个文件的安装路径,这里是message_DIR,就能通过find_package(message REQUIRED)引用message库了。
install(FILES${CMAKE_CURRENT_BINARY_DIR}/messageConfig.cmake${CMAKE_CURRENT_BINARY_DIR}/messageConfigVersion.cmakeDESTINATION${INSTALL_CMAKEDIR})

欢迎留言讨论,创作不易,感谢点赞、关注和收藏~

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Linux之ip命令详解
  • Dockerfile+私有仓库
  • 创新互动体验RAG:利用角色化AI技术增强影视评论的沉浸感
  • [mysql]mysql的演示使用
  • linux下使用xargs批量操作
  • 数据结构与算法的代码实现(C++版)
  • 设计模式 代理模式(Proxy Pattern)
  • 一个简单的CRM客户信息管理系统,提供客户,线索,公海,联系人,跟进信息和数据统计功能(附源码)
  • Maven学习(零基础到面试)
  • 【Qt窗口】—— 浮动窗口
  • DARKTIMES集成到Sui,带来中世纪格斗大逃杀游戏体验
  • 【教程】实测np.fromiter 和 np.array 的性能
  • GCViT实战:使用GCViT实现图像分类任务(一)
  • Django+vue自动化测试平台(29)--测试平台集成playwright录制pytest文件执行
  • LeetCode 算法:杨辉三角 c++
  • [笔记] php常见简单功能及函数
  • 「前端」从UglifyJSPlugin强制开启css压缩探究webpack插件运行机制
  • 2017年终总结、随想
  • Angular 4.x 动态创建组件
  • classpath对获取配置文件的影响
  • Docker下部署自己的LNMP工作环境
  • JavaScript中的对象个人分享
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • Vue 重置组件到初始状态
  • 浮动相关
  • 基于Android乐音识别(2)
  • 前端路由实现-history
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • 浅谈JavaScript的面向对象和它的封装、继承、多态
  • 小程序开发之路(一)
  • 用Canvas画一棵二叉树
  • 在 Chrome DevTools 中调试 JavaScript 入门
  • 正则与JS中的正则
  • Unity3D - 异步加载游戏场景与异步加载游戏资源进度条 ...
  • 资深实践篇 | 基于Kubernetes 1.61的Kubernetes Scheduler 调度详解 ...
  • 昨天1024程序员节,我故意写了个死循环~
  • ​十个常见的 Python 脚本 (详细介绍 + 代码举例)
  • # 睡眠3秒_床上这样睡觉的人,睡眠质量多半不好
  • #07【面试问题整理】嵌入式软件工程师
  • (11)MSP430F5529 定时器B
  • (175)FPGA门控时钟技术
  • (附源码)计算机毕业设计SSM基于健身房管理系统
  • (蓝桥杯每日一题)love
  • (四十一)大数据实战——spark的yarn模式生产环境部署
  • (一)SvelteKit教程:hello world
  • (一)硬件制作--从零开始自制linux掌上电脑(F1C200S) <嵌入式项目>
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • (转)甲方乙方——赵民谈找工作
  • (轉貼) VS2005 快捷键 (初級) (.NET) (Visual Studio)
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .NET Core6.0 MVC+layui+SqlSugar 简单增删改查
  • .NET Framework、.NET Core 、 .NET 5、.NET 6和.NET 7 和.NET8 简介及区别
  • .NET 编写一个可以异步等待循环中任何一个部分的 Awaiter
  • .NET 服务 ServiceController
  • .net的socket示例