内容目录
在我们实际开发过程中,经常不可避免会使用到第三方开源库,这些开源库可能是通过apt-get install
命令自动安装到系统目录中,也可能是由我们自己下载库的源码然后通过编译安装到指令目录下的。
不管哪种方式安装的库文件,如果我们需要自己的项目中使用这些库,首先面临的第一个问题就是如何找到这些库。所谓“找到”这些库,其实是根据我们的需要找到指定版本的库头文件包含路径、链接库路径等,从而能够满足我们开发项目的编译链接需要。
在没有CMake的时代,这种库查找链接的工作都需要借助MakeFile
中的各种命令来完成,非常的繁琐,而且不方便移植,到了CMake时代,CMake给我们提供了find_package()
命令用来查找依赖包,理想情况下,一句find_package()
命令就能把一整个依赖包的头文件包含路径、库路径、库名字、版本号等情况都获取到,后续只管用就好了。但实际使用过程可能会出现这样那样的问题,因此需要我们对find_package()
这个强大的命令有个大概的理解。
本文首先以一个简单Demo案例出发,打印find_package()
配置的环境变量值,以引入find_package()
工作原理(注:IDE CLion)。
main.cpp
:
#include
#include
#include
int main() {
cv::Mat img;
img = cv::imread("../test.png");
namedWindow("Display Image", cv::WINDOW_AUTOSIZE);
imshow("Display Image", img);
cv::waitKey(0);
return 0;
}
CMakeLists.txt
:
cmake_minimum_required(VERSION 3.17)
project(find_package_learning)
set(CMAKE_CXX_STANDARD 14)
add_executable(find_package_learning main.cpp)
find_package(OpenCV REQUIRED)
if(OpenCV_FOUND)
message(STATUS "INFO: Found OpenCV library.")
message(STATUS "INFO: OpenCV_DIR = ${OpenCV_DIR}")
message(STATUS "INFO: OpenCV_INCLUDE_DIRS = ${OpenCV_INCLUDE_DIRS}")
message(STATUS "INFO: OpenCV_LIBS = ${OpenCV_LIBS}")
else()
message("WARNING: OpenCV not found.")
endif()
include_directories(${OPENCV_INCLUDE_DIRS})
target_link_libraries(find_package_learning ${OpenCV_LIBS})
CMake编译(C++源码未发生编译):
-- INFO: Found OpenCV library.
-- INFO: OpenCV_DIR = /usr/local/lib/cmake/opencv4
-- INFO: OpenCV_INCLUDE_DIRS = /usr/local/Cellar/opencv/4.6.0/include/opencv4
-- INFO: OpenCV_LIBS = opencv_calib3d;opencv_core;opencv_dnn;opencv_features2d;opencv_flann;opencv_gapi;opencv_highgui;opencv_imgcodecs;opencv_imgproc;opencv_ml;opencv_objdetect;opencv_photo;opencv_stitching;opencv_video;opencv_videoio;opencv_alphamat;opencv_aruco;opencv_barcode;opencv_bgsegm;opencv_bioinspired;opencv_ccalib;opencv_datasets;opencv_dnn_objdetect;opencv_dnn_superres;opencv_dpm;opencv_face;opencv_freetype;opencv_fuzzy;opencv_hfs;opencv_img_hash;opencv_intensity_transform;opencv_line_descriptor;opencv_mcc;opencv_optflow;opencv_phase_unwrapping;opencv_plot;opencv_quality;opencv_rapid;opencv_reg;opencv_rgbd;opencv_saliency;opencv_sfm;opencv_shape;opencv_stereo;opencv_structured_light;opencv_superres;opencv_surface_matching;opencv_text;opencv_tracking;opencv_videostab;opencv_viz;opencv_wechat_qrcode;opencv_xfeatures2d;opencv_ximgproc;opencv_xobjdetect;opencv_xphoto
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/jb.pan/Desktop/cmake_test/cmake-build-debug
从CMake编译打印的信息可以发现:
find_package()
找到包后会对OpenCV相关的OpenCV_FOUND
、OpenCV_DIR
、OpenCV_INCLUDE_DIRS
、OpenCV_LIBS
等环境变量进行赋值;
环境变量OpenCV_DIR
指向OpenCV库的配置文件.cmake
(名为OpenCVConfig.cmake
)所在的路径,OpenCV_INCLUDE_DIRS
指向OpenCV头文件路径,OpenCV_LIBS
给出所有被包含库文件的名称。
find_package
本质上就是一个搜包的命令,通过一些特定的规则找到包的配置文件,通过执行该配置文件,从而定义了一系列的变量,通过这些变量就可以准确定位到OpenCV库的头文件和库文件,完成编译。
因此find_package工作模式流程大致为:
find_package
指令典型的调用形式为:
find_package( [version])
其中
指需要寻的包名称,[version]
是可选择的库版本号。find_package
指令执行有两种工作模式,这两种工作模式的不同决定了其搜包路径的不同。
Module模式:
find_package
命令基础工作模式(Basic Signature),也是默认工作模式。
指令将依照顺序查找名为Find
的find-module
模块文件(查找顺序如下图),如果find-module
模块文件被找到,它将会被加载以进一步寻找包的组件。find-module
模块文件(即Find
)包含了包的特定配置,包括函数库以及一些其他期望被找到的文件的配置,其内部通过find_library
定位到它们。
Config模式:
find_package
命令高级工作模式(Full Signature)。 只有在find_package()
中指定CONFIG、NO_MODULE等关键字,或者Module模式查找失败后才会进入到Config模式。
该模式通常在定位find-module
模块文件或者在指令中被明确定义时才会执行,该模式下find_package
指令查找包的名为
或者
的配置文件,与Module模式不同,Config模式需要查找的路径非常多,也要匹配很多的可能性,因此有些路径是首先作为根目录,然后进行子目录的匹配。
需要注意的是:1)在第一个路径下查找时,
应当指向包配置文件
或者
所在的路径,如果不指定的话,默认为空,也就是如果不设置
变量,find_package()
无法通过第一个非根目录找到包;2)在随后的两种路径中寻找时,上述路径会被指定为根目录,CMake会首先检查这些根目录路径下是否有名为
或
的模块文件,如果没有,CMake会继续检查或匹配这些根目录
下的以下路径:
/(lib/|lib|share)/cmake/*/
/(lib/|lib|share)/*/
/(lib/|lib|share)/*/(cmake|CMake)/
find_package
通过找到包配置文件/模块文件,进而定义一些列环境变量以实现头文件和库文件的定位,这些环境变量包括
、
、
、
等等,实际上这些环境变量的设置是由包配置文件/模块文件(即.cmake
文件)完成的,以Eigen
库为例,下面给出Eigen
库的包配置文件。
Eigen3Config.cmake
:
# This file exports the Eigen3::Eigen CMake target which should be passed to the
# target_link_libraries command.
####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() #######
####### Any changes to this file will be overwritten by the next CMake run ####
####### The input file was Eigen3Config.cmake.in ########
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)
macro(set_and_check _var _file)
set(${_var} "${_file}")
if(NOT EXISTS "${_file}")
message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
endif()
endmacro()
####################################################################################
if (NOT TARGET eigen)
include ("${CMAKE_CURRENT_LIST_DIR}/Eigen3Targets.cmake")
endif ()
# Legacy variables, do *not* use. May be removed in the future.
set (EIGEN3_FOUND 1)
set (EIGEN3_USE_FILE "${CMAKE_CURRENT_LIST_DIR}/UseEigen3.cmake")
set (EIGEN3_DEFINITIONS "")
set (EIGEN3_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include/eigen3")
set (EIGEN3_INCLUDE_DIRS "${PACKAGE_PREFIX_DIR}/include/eigen3")
set (EIGEN3_ROOT_DIR "${PACKAGE_PREFIX_DIR}")
set (EIGEN3_VERSION_STRING "3.4.0")
set (EIGEN3_VERSION_MAJOR "3")
set (EIGEN3_VERSION_MINOR "4")
set (EIGEN3_VERSION_PATCH "0")
由上面代码可以看出,Eigen3Config.cmake
文件完成了关于Eigen
的所有环境变量设置(Eigen
包没有库文件,EIGEN3_LIBS
未进行设置)。在一些工程项目中,开发人员会依据自己需求自己编写库配置文件,如Robotics Library 中作者自己编写Eigen3
的模块文件FindEigen3
,实现项目中Eigen3
库的定位与环境变量设置,这些cmake文件通常较为复杂并且需要一定经验知识,因此一般开发时通常将库安装好,通过find_package
让CMake
自己寻库即可,具体使用方法请参考Section 3。
1)Module模式的参数为:
find_package( [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])
参数说明:
package
:必填参数。需要查找的包名,在CMake
有大小写区分。
version
和EXACT
:可选参数,version
指定的是版本,如果指定就必须检查找到的包的版本是否和version
兼容。如果指定EXACT
则表示必须完全匹配的版本而不是兼容版本就可以。
QUIET
:可选参数,表示如果查找失败,不会在屏幕进行输出(但是如果指定了REQUIRED
字段,则QUIET
无效,仍然会输出查找失败提示语)。
MODULE
:可选字段。前面提到说“如果Module
模式查找失败则回退到Config
模式进行查找”,但是假如加入了MODULE
选项,那么就只在Module
模式查找,如果Module
模式下查找失败并不切换到Config
模式查找。
REQUIRED
:可选字段。表示一定要找到包,找不到的话就立即停掉整个CMake
。而如果不指定REQUIRED
则CMake
会继续执行。
COMPONENTS
,components
:可选字段,表示查找的包中必须要找到的组件(components),如果有任何一个找不到就算失败,类似于REQUIRED
,导致CMake
停止执行。
2)Config模式的参数为:
find_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模式的参数更多,也更复杂,但实际在使用过程中我们并不会用到所有参数,大部分参数都是可选的,因此一般使用时只需要对Module模式参数了解即可,如需深入了解Config模式参数设置,可参考CMake
官方手册(Link)。
在学会使用find_package
之前,需要充分了解find_package
寻包原理,前面提到find_package
寻包主要有两种方式,Module模式和Config模式,为了充分了解这两种模式,先思考两个问题:
1)find_package
首先会依据Module模式寻找包的模块文件,其名称具有形式Find
,该文件通常存在于CMAKE_MODULE_PATH
和CMake
安装目录下,因此find_package
会依次在这两个目录下寻找。那么疑问来了,如果开发人员需要依赖Qt5
库,而Qt5
库是用户自定义路径安装的,那么CMake
为什么会到CMake
的安装目录里找Qt5
库的模块文件,不是应该在Qt5
的安装目录下么?
答:在用CMake
进行某些库编译安装时,CMake
有时会将该库的模块文件(Module模式)拷贝至CMake
安装目录下,比如我的CMake
安装时的模块文件路径为:C:\Program Files\CMake\share\cmake-3.24\Modules,在这其中,我可以找到当初我用CMake
编译安装的一些库的模块文件。至于在库的安装路径下,也存在配置文件,不过是用于Config模式寻包的,即
或
。
2)在Config模式寻包时,我的库是自定义位置安装的,find_package
是如何找到这个安装位置,并定位到包配置文件
或
?
答:当Module模式无法定位到安装的依赖包时,计算机便会通过Config模式查找,包配置文件通常是安装在包目录下的(不是CMake目录下),由于依赖包在进行安装时并不一定将系统的环境变量设置好,因此Module模式下计算机很可能找不到包具体安装位置,当无法定位到依赖包时,我们通常可以通过设置CMake变量,去指引CMake获得到包配置文件
或
。
当配置开发环境,调用find_package
显示无法找到依赖包时,这说明:1)(Module模式)所依赖的包在安装时没有进行模块文件的安装,或者说包并没有编写模块文件Find
;2)(Config模式)在系统变量中并没有配置包的环境,也没有相关的CMake变量指向包配置文件。因此,为了定位到包通常做法是给定CMake变量,根据Config模式寻包原理,我们可设置的CMake变量包括CMAKE_PREFIX_PATH
等,如:
set(CMAKE_PREFIX_PATH "C:/SFR_SDK/qt-5.12.8")
find_package(Qt5 REQUIRED COMPONENTS ${QT_COMPONENTS})
这种方式并不能保证百分之百奏效,这是由于设置的CMake变量CMAKE_PREFIX_PATH
,指向的是包的根目录,为了定位到包配置文件,CMake还会根据一定的逻辑(详情见章节2.1)去搜寻子目录,但是每个包的安装后目录结构有可能并不遵循这样的逻辑,甚至同一个包在不同系统下的目录结构都会不一样,因此,我们通常还可以直接定位到包配置文件所在目录,如:
set(EIGEN_DIR "C:/SFR_SDK/Eigen3")
find_package(Eigen3 REQUIRED PATHS ${EIGEN_DIR}/share/eigen3/cmake)
或者直接将EIGEN_DIR
设置指向cmake文件夹,然后在寻包时PATHS
参数给定${EIGEN_DIR}
也可。
除此之外,仍需要注意的一点是:find_package
中参数包的名称一定要和
或
的模块文件或者Find
中
名称对应一致(包括大小写),否则即便find_package
给定包的cmake路径,也不可能找到模块/配置文件。
专题blog系列
——————————————————————————-——————————————————————
G . P a n G.Pan G.Pan 先生原创文章,禁止抄袭,转载请注明出处,详情请参考版权声明