【C++】Protobuf与Cmake的使用

前言

最近在移植apollo7.0.0代码时,遇到跨文件夹import依赖Proto文件,采用以前小项目的方式无法成功生成.cc和.h文件,耗费2-3天时间解决,故记录。

文件夹与子文件夹内部依赖(小项目)

  • 项目目录如下:
.
├── CMakeLists.txt
├── PathBackup.proto
├── PathCurrent.proto
├── PathNode.proto
├── PathRouting.proto
├── adas.proto
├── bag_manager.proto
├── behaviors_decision.proto
├── caliLidar.proto
├── chassis.proto
├── control_command.proto
├── hadmap_traffic_light.proto
├── header.proto
├── lane_mark.proto
├── v2x
│   ├── AccelerationSet4Way_PB.proto
│   ├── BSM_PB.proto
│   ├── BasicSafetyMessage_PB.proto
│   ├── BrakeSystemStatus_PB.proto
│   ├── DDateTime_PB.proto
│   ├── EventSource_PB.proto

v2x/ 依赖上层目录的header.proto文件,比如:
import路径仅包含文件名即可

syntax = "proto2";
package common;
import "header.proto";
import "BasicSafetyMessage_PB.proto";

message BSM_PB {
        required BasicSafetyMessage_PB bsmFrame =1;
        optional common.Header header = 2;
}

CMakeLists如下:

  • CPP
cmake_minimum_required(VERSION 3.5)
project(common_proto)
find_package(Protobuf REQUIRED)
set(proto_dir ${CMAKE_CURRENT_SOURCE_DIR})
file(GLOB proto_files "${proto_dir}/*.proto")
file(GLOB proto_files_2 "${proto_dir}/v2x/*.proto")
list(APPEND proto_files ${proto_files_2})

set(proto_gen_cpp_dir ${CMAKE_CURRENT_SOURCE_DIR})

# Create lists of files to be generated
set(proto_gen_cpp_files "")
foreach(proto_file ${proto_files})
  get_filename_component(proto_name ${proto_file} NAME_WE)
  list(APPEND proto_gen_cpp_files
    ${proto_gen_cpp_files}/${proto_name}.pb.cc
  )
endforeach(proto_file ${proto_files})

# Run protoc and generate language-specific headers.
add_custom_command(
  OUTPUT ${proto_gen_cpp_files}
  COMMAND ${PROTOBUF_PROTOC_EXECUTABLE}
    --proto_path=${proto_dir_2}
    --proto_path=${proto_dir}
    --cpp_out=${proto_gen_cpp_dir} ${proto_files}
  DEPENDS ${PROTOBUF_PROTOC_EXECUTABLE} ${proto_files}
  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
set_source_files_properties(${proto_gen_cpp_files} PROPERTIES GENERATED TRUE)

include_directories(
  #${PROTOBUF_INCLUDE_DIR}
  ${PROJECT_SOURCE_DIR}/proto
)
add_library(${PROJECT_NAME} ${proto_gen_cpp_files})
target_link_libraries(${PROJECT_NAME} ${PROTOBUF_LIBRARY})

install(TARGETS ${PROJECT_NAME}
   ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
   LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
   RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
  • PYTHON
cmake_minimum_required(VERSION 3.5)
project(common)

find_package(Protobuf REQUIRED)

set(proto_dir ${PROJECT_SOURCE_DIR}/proto)
file(GLOB proto_files "${proto_dir}/*.proto")
file(GLOB proto_files_2 "${proto_dir}/v2x/*.proto")
list(APPEND proto_files ${proto_files_2})

message(STATUS "Proto Source Dir: ${proto_dir}")
message(STATUS "Proto Source Files: ${proto_files}")

set(proto_gen_py_dir ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION})
file(MAKE_DIRECTORY ${proto_gen_py_dir})

file(WRITE ${proto_gen_py_dir}/__init__.py)
set(proto_gen_py_files "")
foreach(proto_file ${proto_files})
    get_filename_component(proto_name ${proto_file} NAME_WE)
    list(APPEND proto_gen_py_files ${proto_gen_py_dir}/${proto_name}_pb2.py)
endforeach(proto_file ${proto_files})

add_custom_command(
    OUTPUT  ${proto_gen_py_files}
    COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} --proto_path=${proto_dir}/v2x --proto_path=${proto_dir} --python_out=${proto_gen_py_dir}  ${proto_files}
    DEPENDS ${PROTOBUF_PROTOC_EXECUTABLE} ${proto_files}
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
add_custom_target(runa ALL DEPENDS ${proto_gen_py_files})
set_source_files_properties(${proto_gen_py_files} PROPERTIES GENERATED TRUE)

install(DIRECTORY ${proto_gen_py_dir}/
  DESTINATION ${CATKIN_PACKAGE_PYTHON_DESTINATION}
  FILES_MATCHING PATTERN "*.py"
)

install(DIRECTORY include
  DESTINATION ${CATKIN_GLOBAL_INCLUDE_DESTINATION}/common
  FILES_MATCHING PATTERN "*.h"
)

install(DIRECTORY proto
  DESTINATION ${CATKIN_GLOBAL_INCLUDE_DESTINATION}/common
  FILES_MATCHING PATTERN "*.h"
)

文件夹与文件夹之间依赖(大项目)

  • 项目目录如下:
.
├── CMakeLists.txt
├── apollo_planning
│   └── planning_base
│       └── proto
│           └── auto_tuning_raw_feature.proto
└── common_msgs
    ├── BUILD
    ├── CMakeLists.txt
    ├── README.md
    └── basic_msgs
        ├── BUILD
        ├── direction.proto
        ├── drive_event.proto
        ├── drive_state.proto
        ├── error_code.proto
        ├── geometry.proto
        ├── header.proto
        ├── pnc_point.proto
        ├── vehicle_id.proto
        └── vehicle_signal.proto

其中auto_tuning_raw_feature.proto文件如下:

syntax = "proto2";

package apollo.planning.autotuning;

import "common_msgs/basic_msgs/pnc_point.proto";

message PathPointRawFeature {
  optional apollo.common.PathPoint cartesian_coord = 1;
  optional apollo.common.FrenetFramePoint frenet_coord = 2;
}

message SpeedPointRawFeature {
  message ObjectDecisionFeature {
    // obstacle id
    optional int32 id = 1;
    // relative to eog, s_obs - s_host [m]
    optional double relative_s = 2;
    // relative to ego, l_obs - l_host [m]
    optional double relative_l = 3;
    // relative to ego, v_obs - v_host [m/s]
    optional double relative_v = 4;
    // speed [m / s]
    optional double speed = 5;
  }

  optional double s = 1;            // [m]
  optional double t = 2;            // [s]
  optional double v = 3;            // [m/s]
  optional double a = 4;            // [m/s^2]
  optional double j = 5;            // [m/s^3]
  optional double speed_limit = 6;  // speed limit with curvature adj [m/s]

  repeated ObjectDecisionFeature follow = 10;
  repeated ObjectDecisionFeature overtake = 11;
  repeated ObjectDecisionFeature virtual_decision = 13;
  repeated ObjectDecisionFeature stop = 14;
  repeated ObjectDecisionFeature collision = 15;
  repeated ObjectDecisionFeature nudge = 12;
  repeated ObjectDecisionFeature sidepass_front = 16;
  repeated ObjectDecisionFeature sidepass_rear = 17;
  repeated ObjectDecisionFeature keep_clear = 18;
}

// caputuring the obstacle raw distance information from surrounding environment
// based on ST graph
message ObstacleSTRawData {
  message STPointPair {
    optional double s_lower = 1;
    optional double s_upper = 2;
    optional double t = 3;
    optional double l = 4 [default = 10.0];  // filled when nudging
  }

  message ObstacleSTData {
    optional int32 id = 1;
    optional double speed = 2;
    optional bool is_virtual = 3;
    optional double probability = 4;
    repeated STPointPair polygon = 8;
    repeated STPointPair distribution = 9;
  }

  repeated ObstacleSTData obstacle_st_data = 1;
  repeated ObstacleSTData obstacle_st_nudge = 2;
  repeated ObstacleSTData obstacle_st_sidepass = 3;
}

message TrajectoryPointRawFeature {
  optional PathPointRawFeature path_feature = 1;
  optional SpeedPointRawFeature speed_feature = 2;
}

message TrajectoryRawFeature {
  repeated TrajectoryPointRawFeature point_feature = 1;
  optional ObstacleSTRawData st_raw_data = 2;
}

如上auto_tuning_raw_feature.proto引用common_msgs/basic_msgs/pnc_point.proto,但这两个文件不在一个目录下,无法直接使用第一种方式。
如果粗暴一点,就是将所有proto代码放到一个目录下,缺点是import路径全部得改,对于大型项目不太适用,大型项目指apollo,mediapipe等。(项目并不是采用cmake来构建项目,而是使用google自家研发的bazel)
并且,期望生成的.cc和.h文件在原始的proto目录下

CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)
project(MODULES)

set(MODULES_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}") 
message("MODULES_INCLUDE_DIRS: << " ${MODULES_INCLUDE_DIRS})
# set(MODULES_LINKER_LIBS "")
# =============================
# generate proto .cc .h
# =============================
find_package(Protobuf REQUIRED)
# 需要编译的.proto文件:
file(GLOB protobuf_files
        common_msgs/basic_msgs/*.proto
        common_msgs/chassis_msgs/*.proto
        common_msgs/config_msgs/*.proto
        common_msgs/dreamview_msgs/*.proto
        common_msgs/external_command_msgs/*.proto
        common_msgs/localization_msgs/*.proto
        common_msgs/map_msgs/*.proto
        common_msgs/monitor_msgs/*.proto
        common_msgs/planning_msgs/*.proto
        common_msgs/perception_msgs/*.proto
        common_msgs/prediction_msgs/*.proto
        common_msgs/routing_msgs/*.proto
        common_msgs/storytelling_msgs/*.proto
        apollo_planning/planning_base/proto/*.proto
        apollo_planning/planning_base/proto/math/*.proto
        apollo_planning/planning_interface_base/traffic_rules_base/proto/*.proto
        common/util/testdata/*.proto
        common/vehicle_state/proto/*.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 "(/common_msgs.*|/apollo_planning/planning_base/proto.*|/apollo_planning/planning_interface_base/traffic_rules_base/proto.*|/common/util/testdata.*|/common/vehicle_state/proto.*)" 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()
# =============================
# add modules
# =============================
include_directories(
        ${MODULES_INCLUDE_DIRS}
)

参考链接

Protobuf在Cmake中的正确使用 - Oldpan的个人博客
blog.csdn.net

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值