UP | HOME

find_package

Table of Contents

1 How To Find Libraries1

如果软件使用外部库(即软件自身未附带的库),大多数情况下,我们并不知道库(libraries)的头文件和二进制文件是否位于编译软件的系统中。但又必须将正确的头文件目录和二进制文件搜索路径添加到编译命令中,CMake通过提供 find_package 命令来辅助完成此任务。

本文简要讨论如何在 CMake 项目中使用外部库,然后继续讨论如何为尚未拥有的库编写自己的查找模块(module)。

2 Using external libraries

CMake附带了许多模块,可帮助查找各种知名库和包。可通过键入 cmake --help-module-list 或通过查看指定位置的中模块来获取当前 CMake 版本支持的模块列表。 例如,在Ubuntu linux上,模块路径是 /usr/share/cmake/Modules/

以非常受欢迎的 bzip2 库为例。有一个名为 FindBZip2.cmake 的模块。使用 find_package(BZip2) 调用此模块后,cmake 将自动填写各种变量的值,然可以在 CMake 脚本中使用这些值。 有关变量列表,请查看实际的 cmake 模块文件,或者键入 cmake --help-module FindBZip2

例如,考虑一个使用 bzip2 的非常简单的程序 - 即编译器需要知道 bzlib.h 在哪里以及链接器需要找到 bzip2 库(对于动态链接,Unix上是 libbz2.so,Windows上是 libbz2.dll):

cmake_minimum_required(VERSION 2.8)
project(helloworld)
add_executable(helloworld hello.c)
find_package (BZip2)
if (BZIP2_FOUND)
  include_directories(${BZIP_INCLUDE_DIRS})
  target_link_libraries (helloworld ${BZIP2_LIBRARIES})
endif (BZIP2_FOUND)

然后,可以使用 cmake 并使 VERBOSE = 1 来验证是否将正确的标志传递给编译器和链接器。 还可以在编译后使用 “ldd” 或 “dependency walker” 等工具来验证 helloworld 与哪些确切文件相关联。

3 Using external libraries that CMake doesn't yet have modules for

假设要使用 LibXML++ 库。在撰写本文时,CMake没有为 libXML++ 提供“查找”模块。但是通过谷歌搜索 FindLibXML++ Cmake 在互联网上找到了一个名为 FindLibXML++.cmake 的文件。在CMakeLists.txt中,添加如下代码:

find_package(LibXML++ REQUIRED)
include_directories(${LibXML++_INCLUDE_DIRS})
set(LIBS ${LIBS} ${LibXML++_LIBRARIES})

如果包是可选的,可以省略 REQUIRED 关键字并查询布尔变量 LibXML++_FOUND ,看看它是否已被找到。然后,在检测到所有库之后,为目标添加依赖库:

target_link_libraries(exampleProgram ${LIBS})

为此,需要将 FindLibXML++.cmake 文件放入CMake模块路径中。 由于CMake(目前f)未自带该文件,因此必须在包含在项目中。在项目根目录下创建名为 cmake/Modules/ 的文件夹,并在根 CMakeLists.txt 中包含以下代码:

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")

可能已经猜到了,需要放置使用的CMake模块,并且CMake必须在该文件夹中自动查找。

通常这样就可以了。某些库可能需要其他内容,因此请务必查看 FindSomething.cmake 文件以查看该特定库的文档。

4 Packages with components

有些库带有一个或多个依赖库或组件。一个值得注意的例子是Qt库,它带有 QtOpenGL 和 QtXml,以及其他组件。 要使用这两个组件,请使用以下 find_package 命令:

find_package(Qt COMPONENTS QtOpenGL QtXml REQUIRED)

同样,如果包对于您的项目是可选的,则可以省略REQUIRED关键字。在这种情况下,您可以使用 __FOUND 变量(例如Qt_QtXml_FOUND)来检查是否找到了组件。 以下对 find_package 的调用都是等效的:

find_package(Qt COMPONENTS QtOpenGL QtXml REQUIRED)
find_package(Qt REQUIRED COMPONENTS QtOpenGL QtXml)
find_package(Qt REQUIRED QtOpenGL QtXml)

如果某个软件包的一些组件是必选,其他组件可选,则可以调用 find_package 两次:

find_package(Qt COMPONENTS QtXml REQUIRED)
find_package(Qt COMPONENTS QtOpenGL)

或者,可以使用所有组件调用 find_package 一次,但不使用REQUIRED关键字,然后显式检查所需的组件:

find_package(Qt COMPONENTS QtOpenGL QtXml)
if ( NOT Qt_FOUND OR NOT QtXml_FOUND )
  message(FATAL_ERROR "Package Qt and component QtXml required, but not found!")
endif( NOT Qt_FOUND OR NOT QtXml_FOUND )

5 How package finding works

find_package() 命令将查找 Find.cmake 的模块路径,这是查找库的典型方法。首先 CMake检查 ${CMAKE_MODULE_PATH} 中的所有目录,然后查找其自己的模块目录 <CMAKE_ROOT>/share/cmake-x.y/Modules/

如果没有找到这样的文件,它会查找 Config.cmake-config.cmake ,它们应该由库安装(但目前还没有很多库可以安装它们)并且不进行检测,而是只包含已安装库的硬编码值。

前者称为模块模式,后者称为配置模式。此处记录了如何创建配置模式文件。可能还需要用于导入和导出目标的文档。

对于模块系统,其他地方似乎没有文档,所以本文将重点放在它上面。无论使用哪种模式,如果找到包,将定义一组变量:

  • _FOUND
  • _INCLUDE_DIRS or _INCLUDES
  • _LIBRARIES or _LIBRARIES or _LIBS
  • _DEFINITIONS

所有这些都发生在 Find.cmake 文件中。

现在,在代码的顶级目录中的 CMakeLists.txt 文件中(实际上使用该库的客户端代码),检查变量 _FOUND 以查看是否已找到包。对于大多数 包结果变量使用所有大写的包的名称,例如 LIBFOO_FOUND ,对于某些包使用包的准确名称,例如 LibFoo_FOUND 。如果找到此变量,那么,分别讲将 _INCLUDE_DIRS _LIBRARIES 传递给 cmake 的 include_directories() target_link_libraries() 命令。

这些约定记录在 CMake 模块目录的 readme.txt 文件中。

“REQUIRED” 和其他可选的 find_package 参数由 find_package 转发到模块,模块应该根据它们影响它的操作。

6 Piggybacking on pkg-config

Pkg-config是一个基于 “.pc” 文件的构建帮助工具,该文件记录库文件和头文件的位置。它通常出现在类 Unix系统上。有关更多信息,请参阅 pkg-config的站点。 如果 pkg-config 安装在系统上,CMake有自己的功能可以使用它。这些函数记录在 CMake 模块目录下的 FindPkgConfig.cmake 文件中。如果正在处理没有为其构建 cmake 脚本的库,或者正在处理 CMake 的普通查找脚本无法正常工作的情况,这可能特别有用。

但是,如果只是调用 pkg-config 并使用它返回的内容,即使它可用,也应该非常小心。这样做的一个主要原因是,这种方式用户可以通过使用 ccmake 手动定义路径来意外地覆盖或增强库检测,这与 CMake 一样。还有一些情况,pkg-config 提供不正确的信息(错误的编译器等)。在这些情况下,让 CMake 像没有 pkg-config 一样进行检测,但是使用 pkg-config 提供有关查看其他提示的位置。

7 Writing find modules

首先,请注意提供给 find_package 的名称或前缀是文件名的一部分,并且前缀用于所有变量。名称应完全匹配。不幸的是,在许多情况下,即使在CMake附带的模块中,名称也不匹配,从而导致各种问题。

模块的基本操作应大致遵循此顺序:

  • 使用 find_package 检测库所依赖的其他库
    • 转发 QUIETLY 和 REQUIRED 参数(例如,如果当前包是必需的,那么依赖性也应该是)
  • (可选)使用 pkg-config 检测头文件/库路径(如果 pkg-config 可用)
  • 使用 find_path 和 find_library 分别查找头文件和库文件
    • pkg-config 提供的路径仅用作查看位置的提示
    • CMake 还有很多其他硬编码的位置用于查找
    • 结果应保存在变量 _INCLUDE_DIR_LIBRARY 中(注意:单数,不是复数)
  • _INCLUDE_DIRS 设置为 _INCLUDE_DIR
  • _LIBRARIES 设置为 _LIBRARY _LIBRARIES
    • 依赖关系使用复数形式,包本身使用 find_path 和 find_library 定义的单数形式
  • 调用 find_package_handle_standard_args() 宏来设置 _FOUND 变量并打印成功或失败消息。
# - Try to find LibXml2
# Once done this will define
#  LIBXML2_FOUND - System has LibXml2
#  LIBXML2_INCLUDE_DIRS - The LibXml2 include directories
#  LIBXML2_LIBRARIES - The libraries needed to use LibXml2
#  LIBXML2_DEFINITIONS - Compiler switches required for using LibXml2

find_package(PkgConfig)
pkg_check_modules(PC_LIBXML QUIET libxml-2.0)
set(LIBXML2_DEFINITIONS ${PC_LIBXML_CFLAGS_OTHER})

find_path(LIBXML2_INCLUDE_DIR libxml/xpath.h
          HINTS ${PC_LIBXML_INCLUDEDIR} ${PC_LIBXML_INCLUDE_DIRS}
          PATH_SUFFIXES libxml2 )

find_library(LIBXML2_LIBRARY NAMES xml2 libxml2
             HINTS ${PC_LIBXML_LIBDIR} ${PC_LIBXML_LIBRARY_DIRS} )

include(FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set LIBXML2_FOUND to TRUE
# if all listed variables are TRUE
find_package_handle_standard_args(LibXml2  DEFAULT_MSG
                                  LIBXML2_LIBRARY LIBXML2_INCLUDE_DIR)

mark_as_advanced(LIBXML2_INCLUDE_DIR LIBXML2_LIBRARY )

set(LIBXML2_LIBRARIES ${LIBXML2_LIBRARY} )
set(LIBXML2_INCLUDE_DIRS ${LIBXML2_INCLUDE_DIR} )

8 Finding files

然后进行实际检测。提供变量名作为 find_path 和 find_library 的第一个参数。如果需要多个包含路径,请多次调用 find_path 并使用不同变量名称。find_library也是如此。

  • NAMES 为目标指定一个或多个可匹配的名称。在 find_path 中,应该使用主头文件或 C/C++ 代码中的 #included 使用的文件。这也可能包含一个文件夹,例如 alsa/asound.h 然后它将给出 asound.h 所在的文件夹的父目录。
  • PATHS 用于为 CMake 提供额外的搜索路径,并且通常不应该定义为 pkg-config 之外的其他内容(CMake 具有内置默认值,并且可以根据各种配置变量的需要添加更多内容)。如果不使用它,请忽略整个部分。
  • PATH_SUFFIXES(本示例中不存在),某些系统上,库将文件放在 /usr/include/ExampleLibrary-1.23/ExampleLibrary/main.h 等路径。在这种情况下,将使用 NAMES ExampleLibrary/main.h PATH_SUFFIXES ExampleLibrary-1.23 。可以指定多个后缀,并且CMake将在所有头文件目录中尝试它们。

库名称不包含UNIX系统上使用的 lib 前缀,也不包含任何文件扩展名或编译器规范,因为 CMake 将平台独立地检测它们。如果库文件名中包含库版本号,则仍需要写明。

9 使用LibFindMacros

LibFindMacros.cmake 模块是为了使 find-modules 更容易编写。它包含各种 libfind 宏,负责处理所有库始终相同的枯燥部分。有了它,脚本看起来像这样:

# - Try to find ImageMagick++
# Once done, this will define
#
#  Magick++_FOUND - system has Magick++
#  Magick++_INCLUDE_DIRS - the Magick++ include directories
#  Magick++_LIBRARIES - link these to use Magick++

include(LibFindMacros)

# Dependencies
libfind_package(Magick++ Magick)

# Use pkg-config to get hints about paths
libfind_pkg_check_modules(Magick++_PKGCONF ImageMagick++)

# Include dir
find_path(Magick++_INCLUDE_DIR
  NAMES Magick++.h
  PATHS ${Magick++_PKGCONF_INCLUDE_DIRS}
)

# Finally the library itself
find_library(Magick++_LIBRARY
  NAMES Magick++
  PATHS ${Magick++_PKGCONF_LIBRARY_DIRS}
)

# Set the include dir variables and the libraries and let libfind_process do the rest.
# NOTE: Singular variables for this library, plural for libraries this this lib depends on.
set(Magick++_PROCESS_INCLUDES Magick++_INCLUDE_DIR Magick_INCLUDE_DIRS)
set(Magick++_PROCESS_LIBS Magick++_LIBRARY Magick_LIBRARIES)
libfind_process(Magick++)

libfind_pkg_check_modules 是 CMake自己的 pkg-config 模块的便利包装器,旨在简化操作。 不需要测试CMake版本,加载适当的模块,检查它是否已加载等等,而真正想做的只是一个简单的可选检查。 参数与 pkg_check_modules 相同:首先是返回变量的前缀,然后是包名(由pkg-config知道)。 这定义了_INCLUDE_DIRS和其他此类变量。

在第一行中,引入 LibFindMacros。为此,必须将 LibFindMacros.cmake 文件放在模块路径中,因为它当前未在CMake发行版中提供。

10 Dependencies (optional)

libfind_package 的功能与 find_package 类似,不同之处在于它转发了 QUIETLY 和 REQUIRED 参数。 为此,提供的第一个参数是当前包的名称。即 Magick++ 取决于 Magick。 其他参数如版本可以在Magick之后添加,它们只是被转发到 CMake 内部的 find_package 命令。为库所依赖的每个库都安装其中一行,并确保为它们提供查找模块。

11 Final processing

Three items done, four to go. Fortunately, those last ones are rather mechanical and can be taken care of by the libfind_process macro and the last three lines of the example file. You will need to set _PROCESS_INCLUDES with the names of all variables to be included in _INCLUDE_DIRS, and _PROCESS_LIBS with the names of all variables to be included in _LIBRARIES. Then call libfind_process() and it'll do the rest.

可以通过libfind_process 宏和示例文件的最后三行来处理。 您需要将 _PROCESS_INCLUDES 设置为包含在 _INCLUDE_DIRS 中的所有变量的名称,并将 _PROCESS_LIBS 设置为包含在 _LIBRARIES 中的所有变量的名称。然后调用 libfind_process() ,它将完成剩下的工作。

仅当所有提供的变量都具有有效值时,才会将库视为FOUND。

12 Performance and caching

CMake 变量系统比最初看起来要复杂得多。有些变量是缓存的,有些则不是。缓存的变量可以缓存为内部(无法使用 ccmake 进行编辑)或外部缓存(具有类型和文档字符串,可以在 ccmake 中进行修改)。此外,外部变量可以设置为高级,因此它们只能在ccmake的高级模式中看到。

默认情况下,所有变量都是非缓存的。

为了避免在每次运行时再次执行所有库检测,更重要的是允许用户在 ccmake 中设置头文件目录和库,这些都必须进行缓存。幸运的是,这已经由 find_path 和 find_library 处理,它将缓存它们的变量。如果变量已设置为有效值(例如,不是-NOTFOUND或未定义),则这些函数将不执行任何操作,但保留旧值。同样,pkg_check_modules 会对结果进行内部缓存,因此每次都不需要再次调用pkg-config。

另一方面,不应缓存 find 模块(_FOUND,_INCLUDE_DIRS和_LIBRARIES)的输出值,因为修改其他缓存变量将不再导致实际输出发生变化,这显然不是所需的操作。

Footnotes:

Author: liushangliang

Email: phenix3443+github@gmail.com

Created: 2020-04-26 日 10:54