深入理解CMake(6):多个Protobuf版本时让find_package正确选择

package-153360_960_720.png

前面两篇分别分析了apt安装的protobuf和手工编译安装的protobuf,是如何被find_package(Protobuf REQUIRED)找到的。

考虑到实际中可能多人用同一台Ubuntu电脑/服务器,多种需求共存使得apt以及手工安装的protobuf需要共存。甚至手工编译的protobuf需要同时配置多个版本:一个给TensorFlow用,一个给MNN用;又或者是,别人提供的深度学习模型是基于某个版本x.y.z的protoc编译出来的,你要加载它就必须也用x.y.z版本的protobuf库,低版本或高版本都不行。绝了!

深度学习遭Protobuf荼毒久矣,然则要板砖还是要捏着鼻子把Protobuf配置起来。来,咱们看看多个protobuf版本都存在的情况下,怎么配置。

  • 环境依然是ubuntu 16.04;
  • 执行了sudo apt install libprotobuf-dev protobuf-compiler
  • 手工编译安装了protobuf到/home/zz/soft/protobuf-3.8.0

1. 让find_package使用手工编译的Protobuf版本

我们使用“深入理解CMake(5)”中的样例代码,以及CMakeLists.txt的配置,能够找到系统的3.8.0版本的protobuf。换言之,通过设定CMAKE_PREFIX_PATH,系统的apt安装的libprotobuf不会给find_package(Protobuf)造成影响。

关键设定:

set(Protobuf_PREFIX_PATH
    "/home/zz/soft/protobuf-3.8.0/include"
    "/home/zz/soft/protobuf-3.8.0/lib"
    "/home/zz/soft/protobuf-3.8.0/bin"
)
list(APPEND CMAKE_PREFIX_PATH "${Protobuf_PREFIX_PATH}")
find_package(Protobuf REQUIRED)

结果输出:

-- === CMAKE_PREFIX_PATH is: /home/zz/soft/protobuf-3.8.0/include;/home/zz/soft/protobuf-3.8.0/lib;/home/zz/soft/protobuf-3.8.0/bin
-- Found Protobuf: /home/zz/soft/protobuf-3.8.0/lib/libprotobuf.a;-lpthread (found version "3.8.0")
-- === Protobuf_INCLUDE_DIR is: /home/zz/soft/protobuf-3.8.0/include
-- === Protobuf_INCLUDE_DIRS is: /home/zz/soft/protobuf-3.8.0/include
-- Configuring done
-- Generating done
-- Build files have been written to: /home/zz/work/oh-my-cmake/build

然而考虑到在“深入理解(3)”一文中对find_package()的分析,以及modern-cmake的理念,应当让find_package()在CONFIG模式下找到Protobuf。很容易记得/猜到设定Protobuf_DIR变量,然后再调用find_package(),然而你也许会遭遇滑铁卢,不禁发出疑问:我设定了Protobuf_DIR,怎么find_package()还是报错说找不到Protobuf呢?

请尝试检查:

  • Protobuf_DIR的设定是否正确?
    • 指向了不存在的目录吗?那肯定会报错
    • 指向了~/soft/protobuf-3.8.0这样的目录吗?那基本上是不对。它应该是包含ProtobufConfig.cmake或者protobuf-config.cmake文件的目录

当确认Protobuf_DIR设定到了正确路径,例如~/soft/protobuf-3.8.0/lib/cmake/protobuf,清理CMakeCache.txt,再执行cmake,发现找到了protobuf但是版本不对:找到了2.6.1版本的也就是apt安装的版本,而不是自行编译安装的3.8.0版本:

set(Protobuf_DIR "/home/zz/soft/protobuf-3.8.0/lib/cmake/protobuf")
find_package(Protobuf REQUIRED)

-- Detecting CXX compile features - done
-- Found Protobuf: /usr/lib/x86_64-linux-gnu/libprotobuf.so;-lpthread (found version "2.6.1")
-- === Protobuf_INCLUDE_DIR is: /usr/include

虽然我的例子很简单没有报错,但是像MNN或者TensorFlow这样的严格要求Protobuf版本的工程中是会报错的。实际上这里之所以报错,是因为遗漏了非常基础、非常容易被忽略的一点:判断find_package(Protobuf)是在MODULE模式还是CONFIG模式执行查找?!

由于cmake安装路径下提供了FindProtobuf.cmake,使得find(Protobuf REQUIRED)是在MODULE模式下进行检查;而不设定CMAKE_PREFIX_PATH等预设变量、仅设定Protobuf_DIR变量的情况下,并不能进入CONFIG模式做查找,Protobuf_DIR并没有起到作用。

于是乎,觉得:应当同时设定Protobuf_DIR并指定CONFIG或者NO_MODULE字段,才能找到手工编译的Protobuf版本:

set(Protobuf_DIR "/home/zz/soft/protobuf-3.8.0/lib/cmake/protobuf")
find_package(Protobuf REQUIRED CONFIG)

发现运行后不输出任何关于Protobuf的信息;发现还需要手动开启protobuf_MODULE_COMPATIBLE。于是改为:

set(Protobuf_DIR "/home/zz/soft/protobuf-3.8.0/lib/cmake/protobuf")
set(protobuf_MODULE_COMPATIBLE ON CACHE BOOL "")
find_package(Protobuf REQUIRED CONFIG)

发现运行后报错,提示Protobuf_PROTOC_EXECUTABLE有问题,于是手动设定,改为:

set(Protobuf_DIR "/home/zz/soft/protobuf-3.8.0/lib/cmake/protobuf")
set(protobuf_MODULE_COMPATIBLE ON CACHE BOOL "")
set(Protobuf_PROTOC_EXECUTABLE "/home/zz/soft/protobuf-3.8.0/bin/protoc")
find_package(Protobuf REQUIRED CONFIG)

发现运行报错不变。这就比较尴尬了:用的是protobuf官方3.8.0版本的源码编译安装的,执行过make install,而它安装的protobuf-config.cmake和protobuf-module.cmake并不能正常的找到Protobuf_PROTOC_EXECUTABLE也就是protoc可执行文件:

-- Found Threads: TRUE
-- ----- Protobuf_PROTOC_EXECUTABLE is: Protobuf_PROTOC_EXECUTABLE-NOTFOUND
CMake Error at /home/zz/soft/cmake/share/cmake-3.17/Modules/FindPackageHandleStandardArgs.cmake:164 (message):
Could NOT find Protobuf (missing: Protobuf_PROTOC_EXECUTABLE) (found
version "3.8.0.0")
Call Stack (most recent call first):
/home/zz/soft/cmake/share/cmake-3.17/Modules/FindPackageHandleStandardArgs.cmake:445 (_FPHSA_FAILURE_MESSAGE)
/home/zz/soft/protobuf-3.8.0/lib/cmake/protobuf/protobuf-module.cmake:163 (FIND_PACKAGE_HANDLE_STANDARD_ARGS)
/home/zz/soft/protobuf-3.8.0/lib/cmake/protobuf/protobuf-config.cmake:128 (include)
CMakeLists.txt:22 (find_package)

(⊙﹏⊙)这里的输出可以看到,明明找到了protobuf的版本3.8.0.0,但却找不到Protobuf_PROTOC_EXECUTABLE。查看protobuf-module.cmake发现了原因:使用了_protobuf_find_librariesprotoc(为什么一个可执行程序被当作库来查找呢?能找到才怪呢):

# The Protobuf library
_protobuf_find_libraries(Protobuf protobuf)

# The Protobuf Lite library
_protobuf_find_libraries(Protobuf_LITE protobuf-lite)

# The Protobuf Protoc Library
_protobuf_find_libraries(Protobuf_PROTOC protoc)  #!! 这句

if(UNIX)
  _protobuf_find_threads()
endif()

# Set the include directory
get_target_property(Protobuf_INCLUDE_DIRS protobuf::libprotobuf
  INTERFACE_INCLUDE_DIRECTORIES)

# Set the protoc Executable
get_target_property(Protobuf_PROTOC_EXECUTABLE protobuf::protoc
  IMPORTED_LOCATION_RELEASE)
if(NOT EXISTS "${Protobuf_PROTOC_EXECUTABLE}")
  get_target_property(Protobuf_PROTOC_EXECUTABLE protobuf::protoc
    IMPORTED_LOCATION_DEBUG)
endif()
if(NOT EXISTS "${Protobuf_PROTOC_EXECUTABLE}")
  get_target_property(Protobuf_PROTOC_EXECUTABLE protobuf::protoc
    IMPORTED_LOCATION_NOCONFIG)
endif()

# Version info variable
set(Protobuf_VERSION "3.8.0.0")
message(STATUS "----- Protobuf_PROTOC_EXECUTABLE is: ${Protobuf_PROTOC_EXECUTABLE}")

至此,我想对广受Protobuf的CMake查找失败之苦、查找错误之苦的朋友们说一句:你们尽力了,是Protobuf官方的cmake脚本写的太烂;老老实实用MODULE的find_package(Protobuf)吧,CONFIG模式折腾不来

2. cmake使用apt安装的Protobuf

最简单和理想的情况下,一句话即可:

find_package(Protobuf REQUIRED)

而如果不幸的是你的系统上存在诸如~/.cmake/packages/Protobuf/bcf9164940d285f1a324bc985bc00f3a这样的目录,或者指定了CMAKE_PREFIX_PATH,而它里面的路径恰好混合了包括你手工编译的Protobuf和其他库的话(例如没指定安装的prefix,手动编译的包会安装到/usr/local/下的include, bin, lib等目录,此时的/usr/local/lib被放到CMAKE_PREFIX_PATH中),使得apt安装的Protobuf没有被找到。该如何解决呢?

我们知道此时find_package(Protobuf)仍然处于MODULE模式。可以手动指定Protobuf自身相关的3个变量:

Protobuf_INCLUDE_DIR
Protobuf_LIBRARIES
Protobuf_PROTOC_EXECUTABLE

但是我还是不喜欢这样干。万一某天需要换另一个库,库的名字换了,需要手动指定的相关变量的名字也变了,这很麻烦,不通用。

实际上,apt安装的libprotobuf-dev,提供了可用于pkg_config查找的".pc"文件:

(base) arcsoft-43% dpkg -L libprotobuf-dev | grep '.pc'
/usr/lib/x86_64-linux-gnu/pkgconfig/protobuf.pc
/usr/lib/x86_64-linux-gnu/pkgconfig/protobuf-lite.pc

而CMake中是兼容pkg-config的,能利用.pc文件做查找,替代find_package()。具体的原理步骤(包括在Windows下)的使用说明,可以看另一篇: 在cmake中使用pkg-config ;这里仅仅说明对于Protobuf怎么用:

set(ENV{PKG_CONFIG_PATH} /usr/lib/x86_64-linux-gnu/pkgconfig)
find_package(PkgConfig)
pkg_search_module(Protobuf REQUIRED protobuf)

message(STATUS "=== Protobuf_LIBRARIES: ${Protobuf_LIBRARIES}")
message(STATUS "=== Protobuf_INCLUDE_DIRS: ${Protobuf_INCLUDE_DIRS}")
message(STATUS "=== Protobuf_INCLUDE_DIR: ${Protobuf_INCLUDE_DIR}")
message(STATUS "=== Protobuf_INCLUDEDIR: ${Protobuf_INCLUDEDIR}")

输出:

-- Found PkgConfig: /usr/bin/pkg-config (found version "0.29.1")
-- Checking for one of the modules 'protobuf'
-- === Protobuf_LIBRARIES: protobuf;pthread
-- === Protobuf_INCLUDE_DIRS:
-- === Protobuf_INCLUDE_DIR:
-- === Protobuf_INCLUDEDIR: /usr/include

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