目录标题
CMake编译protobuf生成c++代码
本文为个人使用cmake编译protobuf生成源码的经验总结,下面将介绍三种生成protobuf源码的cmake编写方式
1. protobuf_generate_cpp生成源码
cmake提供了FindProtobuf模块,可以通过find_package命令查找Protobuf进行使用,官网给的使用示例如下:
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_cpp(PROTO_SRCS PROTO_HDRS DESCRIPTORS PROTO_DESCS foo.proto)
protobuf_generate_python(PROTO_PY foo.proto)
add_executable(bar bar.cc ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(bar ${Protobuf_LIBRARIES})
这里使用protobuf_generate_cpp命令将foo.proto文件生成源码,使用PROTO_SRC,PROTO_HARS变量分别指代生成的cpp和h文件并可用于连接到target和设置include
不过这种方法有两个缺点:
- 要求protobuf_generate_cpp命令和生成add_executable() 或 add_library() 的命令必须在同一个CMakeList中.
- 该方法(当前3.18)仍无法设置源码的生成路径,只能默认在相应的build-tree中生成
2.使用execute_process命令生成源码
为解决方法一中的缺点,可以使用cmake中的execute_process命令调用protoc程序来自定义生成源码的方法,示例如下:
find_package(Protobuf 3 REQUIRED)
#设置输出路径
(MESSAGE_DIR ${CMAKE_BINARY_DIR}/message)
if(EXISTS "${CMAKE_BINARY_DIR}/message" AND IS_DIRECTORY "${CMAKE_BINARY_DIR}/message")
SET(DST_DIR ${MESSAGE_DIR})
else()
file(MAKE_DIRECTORY ${MESSAGE_DIR})
SET(DST_DIR ${MESSAGE_DIR})
endif()
#设置protoc的搜索路径
LIST(APPEND PROTO_FLAGS -I${CMAKE_SOURCE_DIR}/msg/message)
#获取需要编译的proto文件
file(GLOB_RECURSE MSG_PROTOS ${CMAKE_SOURCE_DIR}/msg/message/*.proto)
set(MESSAGE_SRC "")
set(MESSAGE_HDRS "")
foreach(msg ${MSG_PROTOS})
get_filename_component(FIL_WE ${msg} NAME_WE)
list(APPEND MESSAGE_SRC "${PROJECT_BINARY_DIR}/message/${FIL_WE}.pb.cc")
list(APPEND MESSAGE_HDRS "${PROJECT_BINARY_DIR}/message/${FIL_WE}.pb.h")
# 生成源码
execute_process(
COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} ${PROTO_FLAGS} --cpp_out=${DST_DIR} ${msg}
)
endforeach()
set_source_files_properties(${MESSAGE_SRC} ${MESSAGE_HDRS} PROPERTIES GENERATED TRUE)
在示例中PROTOBUF_PROTOC_EXECUTABLE指代protobuf生成源码的可执行程序protoc,其使用格式为
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto
其中
- -I=$SRC_DIR 为编译时搜索proto的根目录
- –cpp_out=$DST_DIR为源码输出路径
- $SRC_DIR/addressbook.proto 为需要编译的proto文件
在示例代码中
- 首先设置了源码输出路径DST_DIR并且当DST_DIR不存在时生成该目录;
- 之后通过*LIST(APPEND PROTO_FLAGS -I${CMAKE_SOURCE_DIR}/msg/message)*对protoc的搜索路径进行设置;
- 最后execute_process命令遍历中的每个proto文件执行protoc命令,并将生成的源码分别追加到变量MESSAGE_SRC和MESSAGE_HDRS中;
- MESSAGE_SRC和MESSAGE_HDRS可以用于连接target和设置include_directories
这种方法仍然存在缺点:每次执行cmake后,都会重新生成proto源码,导致make时会因为源码变动(内容未变,只是重新生成)而重新编译程序
3.使用add_custom_target与add_custom_command生成源码
为解决方法二中重新编译的问题,在该方法中引入add_custom_target命令生成一个自定义target,并令该target依赖于生成源码的add_custom_command命令
find_package(Protobuf 3 REQUIRED)
#设置输出路径
SET(MESSAGE_DIR ${CMAKE_BINARY_DIR}/message)
if(EXISTS "${CMAKE_BINARY_DIR}/message" AND IS_DIRECTORY "${CMAKE_BINARY_DIR}/message")
SET(PROTO_META_BASE_DIR ${MESSAGE_DIR})
else()
file(MAKE_DIRECTORY ${MESSAGE_DIR})
SET(PROTO_META_BASE_DIR ${MESSAGE_DIR})
endif()
#设置protoc的搜索路径
LIST(APPEND PROTO_FLAGS -I${CMAKE_SOURCE_DIR}/msg/message)
#获取需要编译的proto文件
file(GLOB_RECURSE MSG_PROTOS ${CMAKE_SOURCE_DIR}/msg/message/*.proto)
set(MESSAGE_SRC "")
set(MESSAGE_HDRS "")
foreach(msg ${MSG_PROTOS})
get_filename_component(FIL_WE ${msg} NAME_WE)
list(APPEND MESSAGE_SRC "${PROJECT_BINARY_DIR}/message/${FIL_WE}.pb.cc")
list(APPEND MESSAGE_HDRS "${PROJECT_BINARY_DIR}/message/${FIL_WE}.pb.h")
# 使用自定义命令
add_custom_command(
OUTPUT "${PROJECT_BINARY_DIR}/message/${FIL_WE}.pb.cc"
"${PROJECT_BINARY_DIR}/message/${FIL_WE}.pb.h"
COMMAND ${PROTOBUF_PROTOC_EXECUTABLE}
ARGS --cpp_out ${PROTO_META_BASE_DIR}
-I ${CMAKE_SOURCE_DIR}/msg/message
${msg}
DEPENDS ${msg}
COMMENT "Running C++ protocol buffer compiler on ${msg}"
VERBATIM
)
endforeach()
# 设置文件属性为 GENERATED
set_source_files_properties(${MESSAGE_SRC} ${MESSAGE_HDRS} PROPERTIES GENERATED TRUE)
# 添加自定义target
add_custom_target(generate_message ALL
DEPENDS ${MESSAGE_SRC} ${MESSAGE_HDRS}
COMMENT "generate message target"
VERBATIM
)
可以看到本方法的前一部分与方法二相似,不同点只在于使用了add_custom_command替换了execute_process命令,将add_custom_command的OUTPUT与add_custom_target的DEPENDS绑定,即可依据绑定的target是否变动来决定源码生成命令是否执行(绑定方法:add_custom_command的OUTPUT为MESSAGE_SRC和MESSAGE_HDRS,同时MESSAGE_SRC和MESSAGE_HDRS为add_custom_target的DENPENDS)
其中值得主要的有两点:
- 设置生成的源码文件属性GENERATED为TRUE,否则cmake时会因找不到源码而报错
- 使用add_custom_target添加目标时要设置ALL关键字,否则target将不在默认编译列表中
这样就能实现proto生成源码配置定制化并避免不必要的重新编译了
4.总结
如果只有少量proto文件且在同一文件夹下,可使用方法一的protobuf_generate_cpp生成源码,若proto文件较多且层次复杂,建议使用方法三.
具体使用可以参考该项目https://github.com/mingjitianming/transmit_asio中CMakeLists的使用方式