protobuf是google开发的一个序列化和反序列化的库,通过.proto文件定义文件格式,序列化后的数据是binary的,可以在多语言上使用.一般情况下,若客户端和服务端都是自己做的,用protobuf作为通讯协议,无疑是一个不错的选择.

个人一般喜欢用CMake来管理c++工程.那么,cmake下使用protobuf对个人而言是个很重要的事情.

我准备了一个简单的工程,如此处所示.

cmake有官方的modules,文件是FindProtobuf.cmake,里面有宏PROTOBUF_GENERATE_CPP.用法据介绍如下:

INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR})
PROTOBUF_GENERATE_CPP(PROTO_SRCS PROTO_HDRS foo.proto)
ADD_EXECUTABLE(bar bar.cc ${PROTO_SRCS} ${PROTO_HDRS})
TARGET_LINK_LIBRARIES(bar ${PROTOBUF_LIBRARIES})

很厉害是吧..

现在我有一组文件如下:

├── CMakeLists.txt
├── README.md
├── meta
│   └── proto
│       ├── CMakeLists.txt
│       └── common
│           ├── bar
│           │   ├── CMakeLists.txt
│           │   └── bar.proto
│           └── foo
│               ├── CMakeLists.txt
│               └── foo.proto
└── src
    ├── CMakeLists.txt
    ├── c_proto.cc
    └── c_proto.hh

其中foo.proto文件如下:

message foo_msg 
{
  optional string name = 1;
}

bar.proto文件如下:

import "common/foo/foo.proto";

message bar_msg 
{
  optional foo_msg foo = 1;
  optional string name = 2;
}

如上是对foo_msg和bar_msg的定义,bar引用了foo.开始我是使用的PROTOBUF_GENERATE_CPP那个宏,然后在有import的时候,妥妥的跪了,google家的也表示不解.

然后直接骚扰下cmake的mail list.然后第二天,一个好心的中国人告诉我,他们都是自定义protoc的命令的.

经过一些挣扎修改,最后决定还是手工拼命令.

FIND_PACKAGE(Protobuf REQUIRED)

find宏可以获得PROTOBUF_LIBRARIES,所有用到proto的需要依赖这组lib.

然后在proto的逻辑root目录下设置好参数,把它加入proto flags list中

SET(PROTO_META_BASE_DIR ${CMAKE_CURRENT_BINARY_DIR})
LIST(APPEND PROTO_FLAGS -I${CMAKE_CURRENT_SOURCE_DIR})

然后代码如下:

FILE(GLOB BAR_PROTOS "*.proto")

FOREACH(FIL ${BAR_PROTOS})
  GET_FILENAME_COMPONENT(ABS_FIL ${FIL} ABSOLUTE)
  GET_FILENAME_COMPONENT(FIL_WE ${FIL} NAME_WE)

  LIST(APPEND BAR_SRCS "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc")
  LIST(APPEND BAR_HDRS "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h")

  EXECUTE_PROCESS(
      COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} ${PROTO_FLAGS} --cpp_out=${PROTO_META_BASE_DIR} ${FIL}
      WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
  )
ENDFOREACH()

ADD_LIBRARY(bar ${BAR_SRCS})
TARGET_LINK_LIBRARIES(bar foo)
Categories: Code

Yu

Ideals are like the stars: we never reach them, but like the mariners of the sea, we chart our course by them.

10 Comments

DINGFENG · September 2, 2020 at 21:46

Google Chrome 84.0.4147.135 Google Chrome 84.0.4147.135 Mac OS X  10.16.0 Mac OS X 10.16.0

我也遇到了这个问题,折磨了一整天!设置PROTOBUF_IMPORT_DIRS并不是正确的做法!最新版的cmake仍然没有提供一个直接设置 -I 的参数。

    Yu · September 5, 2020 at 13:39

    Google Chrome 85.0.4183.83 Google Chrome 85.0.4183.83 Mac OS X  10.15.6 Mac OS X 10.15.6

    抱歉很久才看到您的回复。
    目前看来文中的可能是目前最后妥协的结果了。不知道这个能不能解决你的问题。

ZHAO XIAOWEI · October 29, 2018 at 07:34

Google Chrome 70.0.3538.67 Google Chrome 70.0.3538.67 Mac OS X  10.14.0 Mac OS X 10.14.0

文章很赞啊

    yu · October 29, 2018 at 23:46

    Google Chrome 70.0.3538.77 Google Chrome 70.0.3538.77 Mac OS X  10.14.0 Mac OS X 10.14.0

    @ZHAO XIAOWEI 汗,多谢。不过这篇有点久远,我怀疑内容已经过时了。

Neil · April 30, 2016 at 12:27

Google Chrome 49.0.2623.112 Google Chrome 49.0.2623.112 Windows 10 x64 Edition Windows 10 x64 Edition

感谢分享!好文章!

    yu · April 30, 2016 at 14:10

    Google Chrome 50.0.2661.86 Google Chrome 50.0.2661.86 Mac OS X  10.11.4 Mac OS X 10.11.4

    @Neil 多谢

amoylan · January 27, 2016 at 15:55

Opera developer 35.0.2064.0 Opera developer 35.0.2064.0 GNU/Linux x64 GNU/Linux x64

我也试了好久,最终发现不能百分百满足你的要求。两个文件不需要在一起generate,但是bar.proto必须import “foo.proto”才行。 在proto目录中的Cmakelilsts.txt中set(PROTO_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}),在bar目录下set(PROTOBUF_IMPORT_DIRS ${PROTO_ROOT_DIR}/common/foo)即可 公司内网无法上传代码。。

    yu · January 28, 2016 at 17:33

    Google Chrome 47.0.2526.111 Google Chrome 47.0.2526.111 Mac OS X  10.11.3 Mac OS X 10.11.3

    @amoylan 多谢. 尝试很久也无法做得更好了.

amoylan · January 26, 2016 at 17:50

Opera developer 35.0.2064.0 Opera developer 35.0.2064.0 GNU/Linux x64 GNU/Linux x64

博主好,首先来表达一下我对你的敬仰之情,我真的太喜欢这个博客了。
我在工作中遇到了和你一样的问题,一开始我从你这里copy了上述方法,并且成功运行了,于是我叫来架构师大哥,他分分钟找到了更好的办法,删除了我的改动。。。
这里的关键就是那个-I参数,cmake中这个参数名为PROTOBUF_IMPORT_DIRS,可以在源码中找到。因此,在执行protobuf_generate_cpp之前设置一下这个参数的值就可以了

    yu · January 27, 2016 at 01:28

    Google Chrome 47.0.2526.111 Google Chrome 47.0.2526.111 Mac OS X  10.11.3 Mac OS X 10.11.3

    @amoylan 你好, 多谢你的提示.

    我当时也注意到了这个参数. 当时我在文中提到的这个 stackoverflow 的问题里面 也提到了这个问题. 当时我也利用它做了一些操作, 最后能做成的是如这个链接所示的样子. 其中我在 proto 的根目录下配置是这样的. foo.proto 和 bar.proto 两个文件因为有依赖关系, 因此我PROTOBUF_GENERATE_CPP 似乎 必须要一起 generate 才不会报错. 但是若我一起 generate, 那么后果是 foo.pb.cc 和 bar.pb.cc 都跑到了一个文件夹下, 那么我 bar.proto 文件中的引用就大有问题了. bar.proto 的

    import "common/foo/foo.proto";
    

    必须改成

    import "foo.proto";
    

    这样才能生效.

    这并不是我想要的效果.

    如果我设置的 proto 模型如下:

    .
    └── common
        ├── bar
        │   ├── bar.proto
        │   └── foo.proto
        └── foo
            └── foo.proto
    

    那么就会出错. 两个 foo.proto 造成了文件的冲突, 而且 bar.proto 也很难决定是引用哪个 proto 文件.

    我知道同样的文件名并不是个好主意, 但个人还是挺希望兼容这种问题的.

    若可以, 不知可否分享下你的方法? 拜谢.

Leave a Reply to yu Cancel reply

Your email address will not be published. Required fields are marked *