深入理解CMake(4):find_package尋找系統(tǒng)Protobuf(apt)的過程分析

package-153360_960_720.png

先前分析過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>的。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容