先前分析过find_package()
原理,包括MODULE和CONFIG两种模式,每种模式各自的查找顺序也具体进行了解释。本篇以Protobuf为例,一步步确定cmake的find_package(Protobuf)
是如何做到的。
实验基于Ubuntu 16.04系统,使用apt安装的libprotobuf-dev,并且系统里不存在其他版本的protobuf。
1. find_package在MODULE模式下找到Protobuf
find_package(Protobuf REQUIRED) # 能找到
find_package(Protobuf REQUIRED CONFIG) # 找不到
也即是:MODULE模式下找到了protobuf。而MODULE模式下无非是先后从CMAKE_MODULE_PATH
所指示的路径、cmake安装的Modules目录(如~/soft/cmake/share/cmake-3.17/Modules
),根据FindProtobuf.cmake来查找。CMAKE_MODULE_PATH
变量默认为空,而cmake安装目录下的FindProtobuf.cmake
则提供了完整的查找支持。
找到Protobuf后,提供头文件目录、库文件、可执行文件的具体位置/路径等变量:
``Protobuf_FOUND``
Found the Google Protocol Buffers library
(libprotobuf & header files)
``Protobuf_VERSION``
Version of package found.
``Protobuf_INCLUDE_DIRS``
Include directories for Google Protocol Buffers
``Protobuf_LIBRARIES``
The protobuf libraries
``Protobuf_PROTOC_LIBRARIES``
The protoc libraries
``Protobuf_LITE_LIBRARIES``
The protobuf-lite libraries
下面根据FindProtobuf.cmake
的内容,分析它们是如何被确定的。
FindProtobuf.cmake
解读
protobuf的包含目录
使用find_path()
定位了Protobuf_INCLUDE_DIR
:
# Find the include directory
find_path(Protobuf_INCLUDE_DIR
google/protobuf/service.h
PATHS ${Protobuf_SRC_ROOT_FOLDER}/src
)
mark_as_advanced(Protobuf_INCLUDE_DIR)
这里其实埋下了一个坑:如果是自行编译安装的protobuf并且没有提供用于find_package(Protobuf)的脚本的话,你会用find_path
来查找protobuf的头文件搜素目录吗?
实际上,apt装好的libprptobuf-dev把它头文件放在了/usr/include/google/protobuf
目录下,而/usr/include
是系统编译器默认的头文件查找目录。当然能找到了。
protobuf的可执行程序
也就是protoc,是按如下方式确定的:
# Find the protoc Executable
find_program(Protobuf_PROTOC_EXECUTABLE
NAMES protoc
DOC "The Google Protocol Buffers Compiler"
PATHS
${Protobuf_SRC_ROOT_FOLDER}/vsprojects/${_PROTOBUF_ARCH_DIR}Release
${Protobuf_SRC_ROOT_FOLDER}/vsprojects/${_PROTOBUF_ARCH_DIR}Debug
)
mark_as_advanced(Protobuf_PROTOC_EXECUTABLE)
实际上,apt安装的libprotobuf-dev,包含了放在/usr/bin/protoc这一可执行文件,而/usr/bin
默认在PATH环境变量中,因此可以找到。
protobuf的库文件
是这样被查找到的:
# The Protobuf library
_protobuf_find_libraries(Protobuf protobuf)
#DOC "The Google Protocol Buffers RELEASE Library"
_protobuf_find_libraries(Protobuf_LITE protobuf-lite)
过程略繁琐,_protobuf_find_libraries()
函数定义如下:
# Internal function: search for normal library as well as a debug one
# if the debug one is specified also include debug/optimized keywords
# in *_LIBRARIES variable
function(_protobuf_find_libraries name filename)
if(${name}_LIBRARIES)
# Use result recorded by a previous call.
return()
elseif(${name}_LIBRARY)
# Honor cache entry used by CMake 3.5 and lower.
set(${name}_LIBRARIES "${${name}_LIBRARY}" PARENT_SCOPE)
else()
find_library(${name}_LIBRARY_RELEASE
NAMES ${filename}
PATHS ${Protobuf_SRC_ROOT_FOLDER}/vsprojects/${_PROTOBUF_ARCH_DIR}Release)
mark_as_advanced(${name}_LIBRARY_RELEASE)
find_library(${name}_LIBRARY_DEBUG
NAMES ${filename}d ${filename}
PATHS ${Protobuf_SRC_ROOT_FOLDER}/vsprojects/${_PROTOBUF_ARCH_DIR}Debug)
mark_as_advanced(${name}_LIBRARY_DEBUG)
select_library_configurations(${name})
if(UNIX AND Threads_FOUND)
list(APPEND ${name}_LIBRARIES ${CMAKE_THREAD_LIBS_INIT})
endif()
set(${name}_LIBRARY "${${name}_LIBRARY}" PARENT_SCOPE)
set(${name}_LIBRARIES "${${name}_LIBRARIES}" PARENT_SCOPE)
endif()
endfunction()
函数有点长,不过可以看出来,核心查找功能的实现是通过调用find_library()
来查找库文件;分别找debug和release的库,然后用select_library_configurations
来自动修正/设定如下4个变量:
Protobuf_LIBRARY
Protobuf_LIBRARIES
Protobuf_LIBRARY_DEBUG
Protobuf_LIBRARY_RELEASE
显然,这里的find_library()
又是一个核心功能。
find_path()原理解读
find_path()
的作用,是根据提供的一个文件(可以带有前缀子目录),查找到包含该文件的目录。在前面FindProtobuf.cmake中看到,提供google/protobuf/service.h
文件,找到了包含它的目录是/usr/include
,作为find_path()
的输出变量的Protobuf_INCLUDE_DIR
,被设定为/usr/include
。
如果在/usr/include
不存在google/protobuf/service.h
呢?find_path()
有一堆查找规则,每个规则会查找一个或一些目录,只要查找到就不会进入下一个查找规则。可以关闭或定制其中某些规则。
第一个规则是,从<prefix>/include/<arch>
或者<prefix>/include
目录下查找。先不管<arch>,因为目标结果确实不在那里面。是<prefix>
应该取值为什么呢?cmake文档说是从<PackageName>_ROOT
这个cmake变量以及<PackageName>_ROOT
环境变量里面遍历出来的;但实际上这个变量值为空。实测<prefix>
是根据CMAKE_SYSTEM_PREFIX_PATH
来查找的。
作为验证,自行单独写一个CMakeLists.txt进行验证。
先确认确实是第一条规则起作用,找到的头文件目录:
find_path(Protobuf_INCLUDE_DIR
google/protobuf/service.h
PATHS ${Protobuf_SRC_ROOT_FOLDER}/src
NO_DEFAULT_PATH
)
message(STATUS "===== Protobuf_INCLUDE_DIR is: ${Protobuf_INCLUDE_DIR}")
这样就找不到结果了,提示
-- ===== Protobuf_INCLUDE_DIR is: Protobuf_INCLUDE_DIR-NOTFOUND
实际发现,<prefix>
是根据CMAKE_SYSTEM_PREFIX_PATH
来查找的。我这里打印出来是:
/usr/local;/usr;/;/home/zz/soft/cmake;/usr/local;/usr/X11R6;/usr/pkg;/opt
而尝试在find_path()
之前,自行改掉CMAKE_SYSTEM_PREFIX_PATH
,去掉其中的/usr
,也就是:
set(CMAKE_SYSTEM_PREFIX_PATH "/usr/local;/usr;/;/home/zz/soft/cmake;/usr/local;/usr/X11R6;/usr/pkg;/opt")
然后再调用:
find_path(Protobuf_INCLUDE_DIR
google/protobuf/service.h
PATHS ${Protobuf_SRC_ROOT_FOLDER}/src
)
发现这次的结果是找不到。也就是说,我的猜测得到了验证,而这是官方文档(我用3.17版,看得3.17的文档)没说清楚、说错的地方。
还有其他一些个查找头文件的顺序,暂时没看。
find_library()原理解读
仍然在CMakeLists.txt中自行试验:
set(name Protobuf)
set(filename protobuf)
find_library(${name}_LIBRARY_RELEASE
NAMES ${filename}
PATHS ${Protobuf_SRC_ROOT_FOLDER}/vsprojects/${_PROTOBUF_ARCH_DIR}Release
#NO_DEFAULT_PATH
)
mark_as_advanced(${name}_LIBRARY_RELEASE)
message(STATUS "===== Protobuf_LIBRARY_RELEASE is: ${Protobuf_LIBRARY_RELEASE}")
输出:
-- ===== Protobuf_LIBRARY_RELEASE is: /usr/lib/x86_64-linux-gnu/libprotobuf.so
而如果打开注释,也就是设定NO_DEFAULT_PATH
则得到:
-- ===== Protobuf_LIBRARY_RELEASE is: Protobuf_LIBRARY_RELEASE-NOTFOUND
因此,对照find_library的文档,这libprotobuf.so也是第一个查找规则被找到的。
和find_path()一样,find_library()的第一条查找规则也是说的有误导性:说是从<prefix>/lib/<arch>
和<prefix>/lib
里查找,说<prefix>
是从<PackageName>_ROOT
着一个cmake变量和环境变量中遍历得到的。实测仍然是根据CMAKE_SYSTEM_PREFIX_PATH
变量来遍历<prefix>
的。