查找已有库

在我们实际开发过程中,经常不可避免会使用到第三方开源库,这些开源库可能是通过apt-get install命令自动安装到系统目录中,也可能是由我们自己下载库的源码然后通过编译安装到指令目录下的,CMake提供了一个find_package命令用于查找系统中的库。

查找自动安装库:boost

#include <iostream>
#include "lib/math/operations.hpp"
#include <boost/random.hpp>

int main() {
    std::cout<<"Hello CMake!"<<std::endl;
    math::operations op;
    int sum = op.sum(3, 4);
    std::cout<<"Sum of 3 + 4 :"<<sum<<std::endl;
    
    //Boost Random Sample
    boost::mt19937 rng;
    double mean = 2.3;
    double std = 0.34;
    auto normal_dist = boost::random::normal_distribution<double>(mean, std);
    boost::variate_generator<boost::mt19937&,
            boost::normal_distribution<> > random_generator(rng, normal_dist);
    
    for(int i = 0; i < 2; i++){
        auto rand_val = random_generator();
        std::cout<<"Random Val "<<i+1<<" :"<<rand_val<<std::endl;
    }
    return 0;
}

在代码中调用了boost库来产生随机sample,在Ubuntu下可以通过sudo apt-get install libboost-all-dev来安装它。通过apt-get install安装的库系统通常会将其.cmake配置或库文件安装到/usr/lib/usr/local/lib目录下,这也通常是系统默认的搜索路径,所以开发者可以在不指明库调用的情况下使用它们,但这不是一种推荐的方式,正确的CMakeLists.txt应该如下所示:

cmake_minimum_required(VERSION 3.9.1)
project(CMakeHello)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)
message(${CMAKE_BINARY_DIR})

add_executable(cmake_hello main.cpp)
add_subdirectory(lib/math)

find_package(Boost 1.66)
# 可以用find_package(Boost 1.66 REQUIRED)代替一下的检查工作
# Check for libray, if found print message, include dirs and link libraries.
if(Boost_FOUND)
    message("Boost Found")
    include_directories(${Boost_INCLUDE_DIRS})
    target_link_libraries(cmake_hello ${Boost_LIBRARIES})
elseif(NOT Boost_FOUND)
    error("Boost Not Found")
endif()

target_link_libraries(cmake_hello math)

当库被找到之后,find_package命令会自动初始化一些列变量:

  • <NAME>_FOUND : 是否找到的标志

  • <NAME>_INCLUDE_DIRS or <NAME>_INCLUDES: 头文件目录

  • <NAME>_LIBRARIES or <NAME>_LIBRARIES or <NAME>_LIBS : 库文件

  • <NAME>_DEFINITIONS

在找到需要的库之后,需要包含其头文件和库文件。

find_package命令可以得到库文件的绝对路径,所以通常不会生成所谓的<NAME>_LIBRARIES_DIRS再手动添加到link_directories

虽然由于许可证和法律问题等原因,开源并不容易,对于闭源库来说你应该把它的动态库包含金源码树。但对于使用开源库的项目来说,上述并不是一种安全的调用方式,因为你无法确保你的编译主机上具备完整的编译环境(换句话说就是程序的可移植性变差),更推荐的方式为使用源码编译的方式引入第三方库。

查找源码编译库:OpenCV

根据上述规则先给出一个查找并调用OpenCV库的CMakeLists.txt:

cmake_minimum_required(VERSION 3.9.1)
project(find_package_learning)

find_package(OpenCV REQUIRED)
message(STATUS "OpenCV_VERSION = ${OpenCV_VERSION}")
message(STATUS "OpenCV_DIR = ${OpenCV_DIR}")
message(STATUS "OpenCV_INCLUDE_DIRS = ${OpenCV_INCLUDE_DIRS}")
message(STATUS "OpenCV_LIBS = ${OpenCV_LIBS}")

include_directories(${OPENCV_INCLUDE_DIRS})  
add_executable(opencv_test opencv_test.cpp)  
target_link_libraries(opencv_test ${OpenCV_LIBS})

不加验证的,这样执行通常情况下都无法完成构建,除非你的OpenCV在CMake默认搜索路径之下,要想正确查找并调用到OpenCV需要理解find_package的工作原理。

find_package

CMake本身不提供任何搜索库的便捷方法,所有搜索库并给变量赋值的操作必须由CMake代码完成,也即<package_name>Config.cmake以及Find<package_name>.cmake配置文件。

  • Module模式 find_package命令基础工作模式(Basic Signature),也是默认工作模式。

  • Config模式 find_package命令高级工作模式(Full Signature)。 只有在find_package()中指定CONFIG、NO_MODULE等关键字,或者Module模式查找失败后才会进入到Config模式。

find_package的Mode模式

Module模式的参数为:

find_package(<package> [version] [EXACT] [QUIET] [MODULE] [REQUIRED] [[COMPONENTS] [components...]] [OPTIONAL_COMPONENTS components...] [NO_POLICY_SCOPE]

参数解释: package:必填参数。需要查找的包名,注意大小写。

versionEXACT:可选参数,version指定的是版本,如果指定就必须检查找到的包的版本是否和version兼容。如果指定EXACT则表示必须完全匹配的版本而不是兼容版本就可以。

QUIET:可选参数,表示如果查找失败,不会在屏幕进行输出(但是如果指定了REQUIRED字段,则QUIET无效,仍然会输出查找失败提示语)。

MODULE:可选字段。前面提到说“如果Module模式查找失败则回退到Config模式进行查找”,但是假如加入了MODULE选项,那么就只在Module模式查找,如果Module模式下查找失败并不切换到Config模式查找。

REQUIRED:可选字段。表示一定要找到包,找不到的话就立即停掉整个CMake。而如果不指定REQUIRED则CMake会继续执行。

optional_COMPONENTS components:可选字段,表示查找的包中必须要找到的组件(components),如果有任何一个找不到就算失败,类似于REQUIRED,导致CMake停止执行。

  • Module模式下是要查找到名为Find<PackageName>.cmake的配置文件。

  • Module模式只有两个查找路径:CMAKE_MODULE_PATH和CMake安装路径下的Modules目录,先在CMAKE_MODULE_PATH变量对应的路径中查找。如果路径为空,或者路径中查找失败,则在CMake安装目录(即CMAKE_ROOT变量)下的Modules目录下查找。这两个变量可以在CMakeLists.txt文件中打印查看具体内容:

    message(STATUS "CMAKE_MODULE_PATH = ${CMAKE_MODULE_PATH}")
    message(STATUS "CMAKE_ROOT = ${CMAKE_ROOT}")

find_package的Config模式

Config模式的完整命令参数为:

find_package(<package> [version] [EXACT] [QUIET] [REQUIRED] [[COMPONENTS] [components...]] [CONFIG|NO_MODULE] [NO_POLICY_SCOPE] [NAMES name1 [name2 ...]] [CONFIGS config1 [config2 ...]] [HINTS path1 [path2 ... ]] [PATHS path1 [path2 ... ]] [PATH_SUFFIXES suffix1 [suffix2 ...]] [NO_DEFAULT_PATH] [NO_CMAKE_ENVIRONMENT_PATH] [NO_CMAKE_PATH] [NO_SYSTEM_ENVIRONMENT_PATH] [NO_CMAKE_PACKAGE_REGISTRY] [NO_CMAKE_BUILDS_PATH] # Deprecated; does nothing. [NO_CMAKE_SYSTEM_PATH] [NO_CMAKE_SYSTEM_PACKAGE_REGISTRY] [CMAKE_FIND_ROOT_PATH_BOTH | ONLY_CMAKE_FIND_ROOT_PATH | NO_CMAKE_FIND_ROOT_PATH])

相比于Module模式,Config模式的参数更多,也更复杂,但实际在使用过程中我们并不会用到所有参数,大部分参数都是可选的,我们只需要掌握基本的参数用法即可。

其中具体查找库并给<package_name>_INCLUDE_DIRS<package_name>_LIBRARIES两个变量赋值的操作由<package_name>Config.cmake模块完成。

两种模式看起来似乎差不多,不过CMake默认采取Module模式,如果Module模式未找到库,才会采取Config模式。如果XXX_DIR路径下找不到<package_name>Config.cmake文件,则会找/usr/local/lib/cmake/${package_name}/中的<package_name>Config.cmake文件。总之,Config模式是一个备选策略。通常,库安装时会拷贝一份<package_name>Config.cmake到系统目录中,因此在没有显式指定搜索路径时也可以顺利找到。

Config模式查找顺序Config模式下是要查找名为<package_name>Config.cmake<lower-case-package-name>-config.cmake的模块文件。

搜包路径依次为:

  1. 名为<PackageName>_DIR的CMake变量或环境变量路径,默认为空。 这个路径是非根目录路径,需要指定到<PackageName>Config.cmake<lower-case-package-name>-config.cmake文件所在目录才能找到。

  2. 名为CMAKE_PREFIX_PATHCMAKE_FRAMEWORK_PATHCMAKE_APPBUNDLE_PATH的CMake变量或环境变量路径,根目录路径,默认都为空。

  3. PATH环境变量路径,根目录路径,默认为系统环境PATH环境变量值。 这个路径是Config模式大部分情况下能够查找到安装到系统中各种库的原因。 这个路径的查找规则为: 遍历PATH环境变量中的各路径,如果该路径如果以bin或sbin结尾,则自动回退到上一级目录得到根目录。

在上述指明的是根目录路径时,CMake会首先检查这些根目录路径下是否有名为<PackageName>Config.cmake<lower-case-package-name>-config.cmake的模块文件,如果没有,CMake会继续检查或匹配这些根目录下的以下路径(<PackageName>_DIR路径不是根目录路径):

<prefix>/(lib/<arch>|lib|share)/cmake/<name>*/
<prefix>/(lib/<arch>|lib|share)/<name>*/ 
<prefix>/(lib/<arch>|lib|share)/<name>*/(cmake|CMake)/

其中<arch>为系统架构名,如Ubuntu下一般为:/usr/lib/x86_64-linux-gnu,整个lib/<arch>|lib|share为可选路径,例如OpenCV库而言会检查或匹配<prefix>/OpenCV/<prefix>/lib/x86_64-linux-gnu/OpenCV/<prefix>/lib/share/OpenCV/<prefix>/share/OpenCV/等路径;name为包名,不区分大小写<name>*意思是包名后接一些版本后等字符也是合法的,如pcl-1.9也会被找到。

这里不过多赘述如何编写自己的.cmake模块配置文件,创建自己的CMake Package是一个非常复杂的工作,后续将单独写一篇文章来讲解。

查找指定库的建议

虽然通常来说第三方开源库都会提供安装选项,并且安装到CMake(系统)默认的搜索位置,但假如我们希望我们的项目能够跨平台运行,那么就需要使用源码发布的方式,将所有开源的第三方库源码包含在你的源码树中。在这种情况下我们可以参考find_package的搜索路径设置对应的变量即可:

  • 在明确要调用库的<package_name>Config.cmake<lower-case-package-name>-config.cmake路径的情况下,直接设置<package_name>_DIR路径即可:

    set(OpenCV_DIR "${PROJECT_SOURCE_DIR}/thirdparty/opencv-4.2.0/lib/cmake/opencv4")
    
    find_pachage(OpenCV REQUIRED)
  • 假如需要查找多个库,那么建议将所有的.cmake配置文件都放在一个共同的目录下,然后设置CMAKE_PREFIX_PATH变量指向放置这些配置文件的目录,需要注意根据上述的匹配规则,此时每个包的配置文件需要单独放置在命名为包名的文件夹下(文件夹名不区分大小写),否则会提示找不到。

Last updated