
先前分析過find_package()原理,包括MODULE和CONFIG兩種模式,每種模式各自的查找順序也具體進行了解釋。本篇以Protobuf為例,一步步確定cmake的find_package(Protobuf)是如何做到的。
實驗基于Ubuntu 16.04系統(tǒng),使用apt安裝的libprotobuf-dev,并且系統(tǒng)里不存在其他版本的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),根據(jù)FindProtobuf.cmake來查找。CMAKE_MODULE_PATH變量默認為空,而cmake安裝目錄下的FindProtobuf.cmake則提供了完整的查找支持。
找到Protobuf后,提供頭文件目錄、庫文件、可執(zhí)行文件的具體位置/路徑等變量:
``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
下面根據(jù)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是系統(tǒng)編譯器默認的頭文件查找目錄。當然能找到了。
protobuf的可執(zhí)行程序
也就是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這一可執(zhí)行文件,而/usr/bin默認在PATH環(huán)境變量中,因此可以找到。
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()函數(shù)定義如下:
# 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()
函數(shù)有點長,不過可以看出來,核心查找功能的實現(xiàn)是通過調用find_library()來查找?guī)煳募?;分別找debug和release的庫,然后用select_library_configurations來自動修正/設定如下4個變量:
Protobuf_LIBRARY
Protobuf_LIBRARIES
Protobuf_LIBRARY_DEBUG
Protobuf_LIBRARY_RELEASE
顯然,這里的find_library()又是一個核心功能。
find_path()原理解讀
find_path()的作用,是根據(jù)提供的一個文件(可以帶有前綴子目錄),查找到包含該文件的目錄。在前面FindProtobuf.cmake中看到,提供google/protobuf/service.h文件,找到了包含它的目錄是/usr/include,作為find_path()的輸出變量的Protobuf_INCLUDE_DIR,被設定為/usr/include。
如果在/usr/include不存在google/protobuf/service.h呢?find_path()有一堆查找規(guī)則,每個規(guī)則會查找一個或一些目錄,只要查找到就不會進入下一個查找規(guī)則??梢躁P閉或定制其中某些規(guī)則。
第一個規(guī)則是,從<prefix>/include/<arch>或者<prefix>/include目錄下查找。先不管<arch>,因為目標結果確實不在那里面。是<prefix>應該取值為什么呢?cmake文檔說是從<PackageName>_ROOT這個cmake變量以及<PackageName>_ROOT環(huán)境變量里面遍歷出來的;但實際上這個變量值為空。實測<prefix>是根據(jù)CMAKE_SYSTEM_PREFIX_PATH來查找的。
作為驗證,自行單獨寫一個CMakeLists.txt進行驗證。
先確認確實是第一條規(guī)則起作用,找到的頭文件目錄:
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
實際發(fā)現(xiàn),<prefix>是根據(jù)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
)
發(fā)現(xiàn)這次的結果是找不到。也就是說,我的猜測得到了驗證,而這是官方文檔(我用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也是第一個查找規(guī)則被找到的。
和find_path()一樣,find_library()的第一條查找規(guī)則也是說的有誤導性:說是從<prefix>/lib/<arch>和<prefix>/lib里查找,說<prefix>是從<PackageName>_ROOT著一個cmake變量和環(huán)境變量中遍歷得到的。實測仍然是根據(jù)CMAKE_SYSTEM_PREFIX_PATH變量來遍歷<prefix>的。