个人主页: 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()要找的三方库在哪就行。先分析出错的原因:
- 可能是没有安装find_package寻找的三方库。
- 安装了三方库,但是是没设置路径或者路径设置错误。
基本就是上面两种原因,那破案就简单了。针对第一种情况没什么好说的,先安装三方库再说。针对第二种情况,由于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。
- 首先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()
- 一般来说,当依赖一个三方库时最好显示指定三方库安装路径。如果
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设置为头文件路径
- 如果成功找到头文件,则将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)
- 接着使用find_library命令搜索ZeroMQ库。因为库的命名有所不同,这里我们需要区分Unix的平台和Windows平台。
if(NOT ${CMAKE_C_PLATFORM_ID} STREQUAL "Windows")
find_library(ZeroMQ_LIBRARIES
NAMES
libzmq
HINTS
${_ZeroMQ_ROOT}/lib
${_ZeroMQ_ROOT}/lib/x86_64-linux-gnu
)
else()
find_library(ZeroMQ_LIBRARIES
NAMES
zmq
HINTS
${_ZeroMQ_ROOT}/lib
)
endif()
//如果需要区分release和debug
if(NOT ${CMAKE_C_PLATFORM_ID} STREQUAL "Windows")
find_library(ZeroMQ_LIBRARY_RELEASE
NAMES
libzmq
HINTS
${_ZeroMQ_ROOT}/lib
${_ZeroMQ_ROOT}/lib/x86_64-linux-gnu
)
find_library(ZeroMQ_LIBRARY_DEBUG
NAMES
libzmqd
HINTS
${_ZeroMQ_ROOT}/lib
${_ZeroMQ_ROOT}/lib/x86_64-linux-gnu
)
else()
find_library(ZeroMQ_LIBRARY_RELEASE
NAMES
zmq
HINTS
${_ZeroMQ_ROOT}/lib
)
find_library(ZeroMQ_LIBRARY_DEBUG
NAMES
zmqd
HINTS
${_ZeroMQ_ROOT}/lib
)
endif()
- 最后包含了标准FindPackageHandleStandardArgs.cmake,并调用相应的CMake命令。如果找到所有需要的变量,这里是
ZeroMQ_INCLUDE_DIRS
、ZeroMQ_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(ZeroMQ
FOUND_VAR
ZeroMQ_FOUND
REQUIRED_VARS
ZeroMQ_INCLUDE_DIRS
ZeroMQ_LIBRARIES
)
//如果需要区分release和debug
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(ZeroMQ
FOUND_VAR
ZeroMQ_FOUND
REQUIRED_VARS
ZeroMQ_INCLUDE_DIRS
ZeroMQ_LIBRARY_RELEASE
ZeroMQ_LIBRARY_DEBUG
)
Config模式
Config模式相对于Module模式更为复杂些,需要配合目标安装过程,如果使用的三方库是二进制文件并不是源码编译的,建议通过Module模式为三方库提供find_package支持。这里假设我们通过自己编写的源码编译出了message
这样一个target,我们需要通过Config模式为message提供find_package支持。接下来一步步实现这个目标
- 在安装目标过程中添加
EXPORT
关键字,这样CMake将为目标生成一个导出的目标文件。这里的${INSTALL_LIBDIR}
和${INSTALL_BINDIR}
分别为库文件和可执行文件安装目录。
install(
TARGETS
message
EXPORT
messageTargets
ARCHIVE
DESTINATION ${INSTALL_LIBDIR}
COMPONENT lib
RUNTIME
DESTINATION ${INSTALL_BINDIR}
COMPONENT bin
LIBRARY
DESTINATION ${INSTALL_LIBDIR}
COMPONENT lib
PUBLIC_HEADER
DESTINATION ${INSTALL_INCLUDEDIR}/message
COMPONENT dev
)
- 自动生成的导出目标文件称为messageTargets.cmake,需要显式地指定它的安装规则。这里的
${INSTALL_CMAKEDIR}
是CMake配置文件路径,一般设置为库安装路径下的share目录。
install(
EXPORT
messageTargets
NAMESPACE
"message::"
DESTINATION
${INSTALL_CMAKEDIR}
COMPONENT
dev
)
- 需要生成正确的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.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
configure_package_config_file(
${PROJECT_SOURCE_DIR}/cmake/messageConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/messageConfig.cmake
INSTALL_DESTINATION ${INSTALL_CMAKEDIR}
)
// messageConfig.cmake.in
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/messageTargets.cmake")
- 最后安装配置文件。完成后用户在CMake中设定
<package>_DIR
变量指向这两个文件的安装路径,这里是message_DIR
,就能通过find_package(message REQUIRED)
引用message库了。
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/messageConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/messageConfigVersion.cmake
DESTINATION
${INSTALL_CMAKEDIR}
)
欢迎留言讨论,创作不易,感谢点赞、关注和收藏~