深入理解CMake(4):find_package寻找系统Protobuf(apt)的过程分析

package-153360_960_720.png

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352