如果编译软件使用了外部库,事先并不知道它的头文件和链接库的位置。得在编译命令中加上包含它们的查找路径。CMake使用 find_package 命令来解决这个问题。本文讨论了如何在CMake项目中使用外部库,以及如何给没有查找模块的库写一个。 1 FIND_PACKAGEFIND_PACKAGE(
用来调用预定义在 CMAKE_MODULE_PATH 下的 Find
也可以自己定义 Find
version参数 需要一个版本号,它是正在查找的包应该兼容的版本号(格式是major[.minor[.patch[.tweak]]])。 EXACT选项
要求版本号必须精确匹配。如果在find-module内部对该命令的递归调用没有给定[version]参数,那么[version]和EXACT选项会自动地从外部调用前向继承。对版本的支持目前只存在于包和包之间(详见下文)。 QUIET 参数:
会禁掉包没有被发现时的警告信息。对应于Find REQUIRED 参数
其含义是指是否是工程必须的,表示如果报没有找到的话,cmake的过程会终止,并输出警告信息。对应于Find
COMPONENTS参数
在REQUIRED选项之后,或者如果没有指定REQUIRED选项但是指定了COMPONENTS选项,在它们的后面可以列出一些与包相关(依赖)的部件清单(components
list) 示例:
FIND_PACKAGE( libdb_cxx REQUIRED)
1.1 包查找是如何工作的
find_package() 命令会在模块路径中寻找 Find
如果没找到这样的文件,会寻找 前面的称为模块模式,后面的称为配置模式。配置模式的文件的编写见 这里的文档 。可能还会用到 importing and exporting targets 这篇文档。 模块系统好像还没有文档,所以本文主要讨论这方面的内容。 不管使用哪一种模式,只要找到包,就会定义下面这些变量:
这些都在 Find
现在,在你的代码(要使用库 这些约定的文档在CMake模块目录中的 readme.txt 文件中。 REQUIRED 和其他可选的 find_package 的参数被 find_package 传给模块,模块由此确定操作。
用户代码总体上应该使用上述的简单调用格式查询需要的包。本命令文档的剩余部分则详述了find_package的完整命令格式以及具体的查询过程。期望通过该命令查找并提供包的项目维护人员,我们鼓励你能继续读下去。
该命令在搜索包时有两种模式:“模块”模式和“配置”模式。当该命令是通过上述的精简格式调用的时候,用的就是模块模式。在该模式下,CMake搜索所有名为Find 完整的配置模式下的命令格式是: find_package( NO_MODULE可以用来明确地跳过模块模式。它也隐含指定了不使用在精简格式中使用的那些选项。
配置模式试图查找一个由待查找的包提供的配置文件的位置。包含该文件的路径会被存储在一个名为
通过使用CONFIGS选项可以改变可能的配置文件的名字。以下描述搜索的过程。如果找到了配置文件,它将会被CMake读取并处理。由于该文件是由包自身提供的,它已经知道包中内容的位置。配置文件的完整地址存储在cmake的变量
所有CMake要处理的配置文件将会搜索该包的安装信息,并且将该安装匹配的适当版本号(appropriate version)存储在cmake变量
如果没有找到包配置文件,CMake将会生成一个错误描述文件,用来描述该问题——除非指定了QUIET选项。如果指定了REQUIRED选项,并且没有找到该包,将会报致命错误,然后配置步骤终止执行。如果设置了
如果给定了[version]参数,那么配置模式仅仅会查找那些在命令中请求的版本(格式是major[.minor[.patch[.tweak]]])与包请求的版本互相兼容的那些版本的包。如果指定了EXACT选项,一个包只有在它请求的版本与[version]提供的版本精确匹配时才能被找到。CMake不会对版本数的含义做任何的转换。包版本号由包自带的版本文件来检查。对于一个备选的包配置文件 PACKAGE_FIND_NAME = 版本文件会检查自身是否满足请求的版本号,然后设置了下面这些变量: PACKAGE_VERSION = 提供的完整的版本字符串。 PACKAGE_VERSION_EXACT = 如果版本号精确匹配,返回true。 PACKAGE_VERSION_COMPATIBLE = 如果版本号相兼容,返回true。 PACKAGE_VERSION_UNSUITABLE = 如果不适合任何版本,返回true。 下面这些变量将会被find_package命令检查,用以确定配置文件是否提供了可接受的版本。在find_package命令返回后,这些变量就不可用了。如果版本可接受,下述的变量会被设置:
然后,对应的包配置文件才会被加载。当多个包配置文件都可用时,并且这些包的版本文件都与请求的版本兼容,选择哪个包将会是不确定的。不应该假设cmake会选择最高版本或者是最低版本。(以上的若干段是对find_package中版本匹配步骤的描述,并不需要用户干预——译注。) 配置模式提供了一种高级接口和搜索步骤的接口。这些被提供的接口的大部分是为了完整性的要求,以及在模块模式下,包被find-module加载时供内部使用。大多数用户仅仅应该调用: find_package(
来查找包。鼓励那些需要提供CMake包配置文件的包维护人员应该命名这些文件并安装它们,这样下述的整个过程将会找到它们而不需要使用附加的选项。 CMake为包构造了一组可能的安装前缀。在每个前缀下,若干个目录会被搜索,用来查找配置文件。下述的表格展示了待搜索的路径。每个条目都是专门为Windows(W),UNIX(U)或者Apple(A)约定的安装树指定的。
在支持OS X平台和Application Bundles的系统上,包含配置文件的框架或者bundles会在下述的路径中被搜索:
在所有上述情况下,
这些路径集用来与那些在各自的安装树上提供了配置文件的工程协作。上述路径中被标记为(W)的是专门为Windows上的安装设置的,其中的 安装前缀是通过以下步骤被构建出来的。如果指定了NO_DEFAULT_PATH选项,所有NO_*选项都会被激活。 1、搜索在cmake特有的cache变量中指定的搜索路径。这些变量是为了在命令行中用-DVAR=value选项指定而设计的。通过指定NO_CMAKE_PATH选项可以跳过该搜索路径。搜索路径还包括: CMAKE_PREFIX_PATH CMAKE_FRAMEWORK_PATH CMAKE_APPBUNDLE_PATH 2、搜索cmake特有的环境变量。这些变量是为了在用户的shell配置中进行配置而设计的。通过指定NO_CMAKE_ENVIRONMENT_PATH选项可以跳过该路径。搜索的路径包括:
3、搜索HINTS选项指定的路径。这些路径应该是由操作系统内省时计算产生的,比如由其它已经找到的项的位置而提供的线索。硬编码的参考路径应该在PATHS选项中指定。 4、搜索标准的系统环境变量。如果指定了NO_SYSTEM_ENVIRONMENT_PATH选项,这些路径会被跳过。以"/bin"或"/sbin"结尾的路径条目会被自动转换为它们的父路径。搜索的路径包括: PATH 5、搜索在CMake GUI中最新配置过的工程的构建树。可以通过设置NO_CMAKE_BUILDS_PATH选项来跳过这些路径。这是为了在用户正在依次构建多个相互依赖的工程时而准备的。
6、搜索存储在CMake用户包注册表中的路径。通过设置NO_CMAKE_PACKAGE_REGISTRY选项可以跳过这些路径。当CMake嗲用export(PACKAGE 7、搜索在当前系统的平台文件中定义的cmake变量。可以用NO_CMAKE_SYSTEM_PATH选项跳过这些路径。 CMAKE_SYSTEM_PREFIX_PATH CMAKE_SYSTEM_FRAMEWORK_PATH CMAKE_SYSTEM_APPBUNDLE_PATH 8、搜索由PATHS选项指定的路径。这些路径一般是硬编码的参考路径。 在Darwin或者支持OS X 框架的系统上,cmake变量CMAKE_FIND_FRAMEWORK可以用来设置为空,或者下述值之一:
"FIRST" - 在标准库或头文件之前查找框架。在Darwin系统上这是默认选项。 "LAST" - 在标准库或头文件之后查找框架。 "ONLY" - 仅仅查找框架。 "NEVER" - 从不查找框架。 在Darwin或者支持OS X Application Bundles的系统,cmake变量CMAKE_FIND_APPBUNDLE可以被设置为空或者下面这些值中的一个: "FIRST" - 在标准库或头文件之前查找application bundles。在Darwin系统上这是默认选项。 "LAST" - 在标准库或头文件之后查找application bundles。 "ONLY" - 仅仅查找application bundles。 "NEVER" - 从不查找application bundles。 CMake变量CMAKE_FIND_ROOT_PATH指定了一个或者多个优先于其他搜索路径的搜索路径。该变量能够有效地重新定位在给定位置下进行搜索的根路径。该变量默认为空。当使用交叉编译时,该变量十分有用:用该变量指向目标环境的根目录,然后CMake将会在那里查找。默认情况下,在CMAKE_FIND_ROOT_PATH中列出的路径会首先被搜索,然后是“非根”路径。该默认规则可以通过设置CMAKE_FIND_ROOT_PATH_MODE_LIBRARY做出调整。在每次调用该命令之前,都可以通过设置这个变量来手动覆盖默认行为。如果使用了NO_CMAKE_FIND_ROOT_PATH变量,那么只有重定位的路径会被搜索。 默认的搜索顺序的设计逻辑是按照使用时从最具体到最不具体。通过多次调用find_library命令以及NO_*选项,可以覆盖工程的这个默认顺序: find_library( NAMES name PATHS paths... NO_DEFAULT_PATH) find_library( NAMES name) 只要这些调用中的一个成功返回,结果变量就会被设置并且被存储到cache中;这样随后的调用都不会再行搜索。如果那找到的库是一个框架,VAR将会被设置为指向框架“<完整路径>/A.framework” 的完整路径。当一个指向框架的完整路径被用作一个库文件,CMake将使用-framework A,以及-F<完整路径>这两个选项将框架连接到目标上。 参见cmake_policy()命令的文档中关于NO_POLICY_SCOPE选项讨论。 2 使用cmake自带查找模块的外部库为了能支持各种常见的库和包,CMake自带了很多模块。可以通过命令 cmake --help-module-list (输入cmake --help,然后双击Tab会有命令提示)得到你的CMake支持的模块的列表: cmake version 2.8.12.2 ... ... 或者直接查看模块路径。比如Ubuntu linux上,模块的路径是 ls /usr/share/cmake/Modules/: AddFileDependencies.cmake CPackDeb.cmake FindOpenThreads.cmake FindBZip2.cmake ... ... 让我们以bzip2库为例。CMake中有个 FindBZip2.cmake 模块。只要使用 find_package(BZip2) 调用这个模块,cmake会自动给一些变量赋值,然后就可以在CMake脚本中使用它们了。变量的列表可以查看cmake模块文件,或者使用命令 cmake --help-module FindBZip2 : cmake version 2.8.12.2 比如一个使用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 和 make VERBOSE=1 来验证传给编译器和链接器的flag是否正确。也可以用ldd或者dependency walker之类的工具在编译后验证 helloworld 链接的文件。 3 使用cmake没有自带查找模块的外部库假设你想要使用LibXML++库。在写本文时,CMake还没有一个libXML++的查找模块。但是可以在网上搜索到一个( 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的模块路径(/usr/share/cmake/Modules/)。因为CMake还不包含它,需要在项目中指定。在自己的项目根目录下创建一个 cmake/Modules/ 文件夹,并且在主 CMakeLists.txt 中包含下面的代码: set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") 把刚才的需要用到的CMake模块放到这个文件夹下。 一般来说就是这样。有些库可能还需要些其他的什么,所以要再看一下 FindSomething.cmake 文件的文档。 3.1 包含组件的依赖包有些库不是一个整体,还包含一些依赖的库或者组件。一个典型的例子是Qt库,它其中包含QtOpenGL和QtXml组件。使用下面的 find_package 命令来使用这些组件: find_package(Qt COMPONENTS QtOpenGL QtXml REQUIRED) 如果包是可选的,这里同样可以忽略 REQUIRED 关键字。这时可以使用 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) 或者也可以不加 REQUIRED 关键字用 find_package 同时查找全部组件,然后再显式地检查必需的组件: 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 ) 4 捎带介绍下pkg-configpkg-config是个用来帮助构建的工具,它基于记录库文件和头文件位置的 .pc 文件。主要用在类Unix系统上。可以在pkg-config的网站 找到更多的信息。CMake可以利用pkg-config,可以在CMake的模块目录下的 FindPkgConfig.cmake 文件中找到相关的文档。这在当你处理一个没有cmake脚本的库的时候,或者遇到CMake的查找脚本失效的情况,非常有帮助。 但是,直接使用pkg-config的结果需要非常小心。一个主要原因是对于ccmake手动定义的库路径,可能覆盖到或者发生冲突。此外,也有可能pkg-config提供了错误的信息(错误的编辑器等)。对于这些情况,让CMake不依赖pkg-config做检测,而只用pkg-config作为查找路径的提示。 5 编写查找模块首先,注意传给 find_package 的名字或者前缀,是用于全部变量的部分文件名和前缀。这很重要,名字必须完全匹配。不幸的是很多情况下,即使是CMake自带的模块,也有不匹配的名字,导致各种问题。 模块的基本操作应该大体按下面的顺序:
# - 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} ) set(LIBXML2_LIBRARIES ${LIBXML2_LIBRARY} ) set(LIBXML2_INCLUDE_DIRS ${LIBXML2_INCLUDE_DIR} ) 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 ) (这一段看不懂,应该是排版错误,是5.2的。 第一行包含了LibFindMacros。因为当前CMake中并没有,所以要想生效,就必须把 LibFindMacros.cmake 文件放到模块路径下。) 5.1 查找文件然后是实际的检测。给 find_path 和 find_library 提供一个变量名作为第一个参数。如果你需要多个 include 路径,用不同的变量名多次调用 find_path , find_library 类似。 NAMES 指定目标的一个或多个名字,只要匹配上一个,就会选中它。在 find_path 中应该使用主头文件或者C/C++代码导入的文件。也有可能会包含目录,比如 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_SUFFIXESExampleLibrary-1.23 。可以指定多个后缀,CMake会在所有包含的目录和主目录逐一尝试,也包括没有后缀的情况。 库名不包括UNIX系统上使用的前缀,也不包括任何文件扩展名或编译器标准之类的,CMake会不依赖平台地检测它们。如果库文件名中有库的版本号,那么它仍然需要。 5.2 使用LibFindMacros有一个 LibFindMacros.cmake 文件,用来便于写查找模块。它包含对于每个库都相同的各种 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++) 第一行包含了LibFindMacros。因为当前CMake中并没有,所以要想生效,就必须把
LibFindMacros.cmake 文件放到模块路径下。 libfind_pkg_check_modules 是CMake自己的pkg-config模块的一个用来简化的封装。你不用再检查CMake的版本,加载合适的模块,检查是否被加载,等等。参数和传给 pkg_check_modules 的一样:先是待返回变量的前缀,然后是包名(pkg-config的)。这样就定义了 5.2.1 依赖(可选)libfind_package 和 find_package 类似,区别是它转发 QUIETLY 和 REQUIRED 参数。第一个参数是当前的包名。即,这里Magick++依赖于Magick。其他参数比如版本可以添加在Magick后面,它们被转发给CMake的内部 find_package命令。对你的库依赖的每个库加上其中一行,并且提供查找模块。 5.2.2 最后处理最后的处理,幸运的是非常程序化,可以通过 libfind_process 宏和示例中的最后三行来完成。你需要把 只有提供的全部变量都有有效值时,库被认为 FOUND 。 6 性能和缓存CMake的变量系统要比初看起来的要复杂得多。有些变量做了缓存。做了缓存的变量有内部的(不能用ccmake编辑)和外部的(可以被ccmake修改)。另外,外部变量只能在ccmake的高级模式可见。 默认情况下,所有变量都是不缓存的。 为了避免每次执行时都重复检测全部的库,更为了允许用户在ccmake中设置include目录和库,需要支持缓存。幸运的是,这已经被 find_path 和 find_library 支持,它们可以缓存它们的变量。如果变量已经设置为有效值(比如不是 -NOTFOUND 或者未定义),这些函数将什么也不做,保持旧值。类似地, pkg_check_modules 支持结果的内部缓存,因此不需要每次都再调用pkg-config。 另一方面,查找模块的输出值( 7 查找模块的常见问题
|
|