Protobuf在Cmake中的正確使用

Protobuf是google開發的一個序列化和反序列化的協議庫,咱們能夠本身設計傳遞數據的格式,經過.proto文件定義咱們的要傳遞的數據格式。例如,在深度學習中經常使用的ONNX交換模型就是使用.proto編寫的。咱們能夠經過多種前端(MNN、NCNN、TVM的前端)去讀取這個.onnx這個模型,可是首先你要安裝protobuf。前端

在以前的博文中已經簡單介紹了onnx,其中onnx.proto就表明了onnx模型的基本數據結構。通常來講,protobuf常常搭配Cmake使用,Cmake有官方的modules,能夠經過簡單的幾個命令protobuf_generate_cpp來生成對應的.pb.cc.pb.hpython

簡單的例子:git

find_package(Protobuf REQUIRED)
include_directories(${Protobuf_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS foo.proto)
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS EXPORT_MACRO DLL_EXPORT foo.proto)
protobuf_generate_python(PROTO_PY foo.proto)
add_executable(bar bar.cc ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(bar ${Protobuf_LIBRARIES})

可是這個例子太簡單了,若是咱們的.proto文件只有一個或者說都只在一個目錄裏,那用這個命令沒什麼毛病...github

但若是是這種狀況,咱們的文件目錄以下:api

├── 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;
}

如上,bar文件引用foo,並且這兩個不在一個目錄,若是直接使用protobuf_generate_cpp來生成,直接會報錯。(這個例子取自Yu的一篇博文ui

也想過把他倆放到同一個目錄...而後bar.proto中import的代碼就要修改,雖然這樣能夠,但顯然是不適合大型的項目。google

而這個大型項目顯然就是mediapipe...折磨了我很久。debug

關於mediapipe的詳細介紹在另外一篇文章。mediapipe中使用了大量的ProtoBuf技術來表示圖結構,並且mediapipe原生並非採用cmake來構建項目,而是使用google自家研發的bazel,這個項目構建系統我就不評價了,而如今我須要使用Cmake來對其進行構建。

mediapipe-API構架

這也是噩夢的開始,mediapipe的.proto文件不少,核心的framework的目錄下存在不少的.proto文件,根目錄和子目錄都有.proto文件:

media-framework

並且每一個proto文件之間存在引用的順序,framework根目錄下的calculator.proto文件:

// mediapipe/framework/calculator.proto
syntax = "proto3";

package mediapipe;

import public "mediapipe/framework/calculator_options.proto";

import "google/protobuf/any.proto";
import "mediapipe/framework/mediapipe_options.proto";
import "mediapipe/framework/packet_factory.proto";
import "mediapipe/framework/packet_generator.proto";
import "mediapipe/framework/status_handler.proto";
import "mediapipe/framework/stream_handler.proto";

每一個.proto文件都import了其餘目錄下的文件,這裏的import相似於C++中的include,可是這裏的import又能夠相互引用,例如上述的status_handler.proto也引用了mediapipe_options.proto

若是直接對上述全部的.proto文件直接使用protobuf_generate_cpp命令,會直接報錯,由於這些文件不在一個目錄,並且import的相對目錄也沒法分析。另外,不一樣目錄內的.cc文件會引用相應目錄生成的.pb.h文件,咱們須要生成的.pb.cc.pb.h在原始的目錄中,這樣才能夠正常引用,要否則須要修改其餘源代碼的include地址,比較麻煩。

CLion中Cmake來編譯proto生成的.pb.cc.pb.h不在原始目錄,而是集中在cmake-build-debug(release)中,咱們額外須要將其中生成的.pb.cc.pb.h文件移動到原始地址(Clion的狀況是這樣)。

正確修改cmake

對於這種狀況,比較合適的作法是直接使用命令進行生成。

首先找到全部須要編譯的.proto文件:

file(GLOB protobuf_files
        mediapipe/framework/*.proto
        mediapipe/framework/tool/*.proto
        mediapipe/framework/deps/*.proto
        mediapipe/framework/testdata/*.proto
        mediapipe/framework/formats/*.proto
        mediapipe/framework/formats/annotation/*.proto
        mediapipe/framework/formats/motion/*.proto
        mediapipe/framework/formats/object_detection/*.proto
        mediapipe/framework/stream_handler/*.proto
        mediapipe/util/*.proto
        mediapipe/calculators/internal/*.proto
        )

接下來,定義相關的目錄地址,PROTO_META_BASE_DIR爲編譯以後生成文件的目錄。PROTO_FLAGS很重要,指定編譯.proto文件時的總的尋找路徑,.proto中的import命令根據根據這個地址去鏈接其餘的.proto文件:

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

設置好以後,經過FOREACH去循環以前的.proto文件,依次編譯每一個文件,而後將生成的.pb.cc.pb.h移動回原始的目錄,至此就能夠正常工做了。

FOREACH(FIL ${protobuf_files})

    GET_FILENAME_COMPONENT(FIL_WE ${FIL} NAME_WE)

    string(REGEX REPLACE ".+/(.+)\\..*" "\\1" FILE_NAME ${FIL})
    string(REGEX REPLACE "(.+)\\${FILE_NAME}.*" "\\1" FILE_PATH ${FIL})

    string(REGEX MATCH "(/mediapipe/framework.*|/mediapipe/util.*|/mediapipe/calculators/internal/)" OUT_PATH ${FILE_PATH})

    set(PROTO_SRCS "${CMAKE_CURRENT_BINARY_DIR}${OUT_PATH}${FIL_WE}.pb.cc")
    set(PROTO_HDRS "${CMAKE_CURRENT_BINARY_DIR}${OUT_PATH}${FIL_WE}.pb.h")

    EXECUTE_PROCESS(
            COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} ${PROTO_FLAGS} --cpp_out=${PROTO_META_BASE_DIR} ${FIL}
    )
    message("Copying " ${PROTO_SRCS} " to " ${FILE_PATH})

    file(COPY ${PROTO_SRCS} DESTINATION ${FILE_PATH})
    file(COPY ${PROTO_HDRS} DESTINATION ${FILE_PATH})

ENDFOREACH()

參考連接

http://blog.argcv.com/articles/3884.c
https://www.v2ex.com/t/602363
https://stackoverflow.com/questions/29720410/no-member-found-when-use-cmake-construct-proto/29817843#answer-29727925

撩我吧

  • 若是你與我志同道合於此,老潘很願意與你交流;
  • 若是你喜歡老潘的內容,歡迎關注和支持。
  • 若是你喜歡個人文章,但願點贊👍 收藏 📁 評論 💬 三連一下~

想知道老潘是如何學習踩坑的,想與我交流問題~請關注公衆號「oldpan博客」。
老潘也會整理一些本身的私藏,但願能幫助到你們,點擊神祕傳送門獲取。

相關文章
相關標籤/搜索