本文主要提供一种在 C\C++ 项目中嵌入 Protobuf/Protobuf-C proto 源文件进行编译的应用方法。
Protobuf 的 proto 源文件在 C\C++ 项目构建需要首先通过 protoc 工具生成对应的 C++ 源码文件,然后参与编译过程。将这个工具生成过程也集成到 CMake 构建中会方便 proto 定义变更的及时跟进,也可以将 proto 文件同时纳入版本管理中。
在 CMake 中可以使用 execute_process
来手动调用 protoc
来进行源码生成,但是这样在每次 cmake 命令时都重新生成源码文件,导致依赖的其他构建目标也重新编译,在 proto 定义未修改的情况下无端增加了编译时间。本文提供的方法可以避免这个问题。
主要思路如下:
- proto 文件构建作为 cmake 工程一个的 subdirectory。允许多个 proto 文件参与构建,同时将生成的源码编译为库作为其他目标的链接对象,而不是以源码形式参与其他目标的编译。
- 利用 cmake 的
add_custom_command
和 add_custom_target
进行 proto 文件生成。将生成源码文件与 proto 源文件增加依赖关系,只有当 proto 文件更改时才需要重新生成。
在项目中,将项目使用的 proto 文件放到一个子目录比如 proto
中,在目录中同时增加一个 CMakeLists.txt
作为项目 subdirectory
。项目主 CMakeLists.txt
去 add_subdirectory
这个 proto
目录,然后 include_directories
对应的头文件目录变量。
以下为 proto
中的 CMakeLists.txt
示例,同时包括了 protobuf 和 protobuf-c 两个示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
# 获取编译器
find_program(
PROTOC_C
protoc-c
DOC "Protobuf-c Compiler (protoc-c)"
REQUIRED
)
find_program(
PROTOC_CXX
protoc
DOC "Protobuf Compiler (protoc)"
REQUIRED
)
# 需要编译的 proto 文件
file (GLOB PROTO_SOURCE_FILES
"${CMAKE_CURRENT_SOURCE_DIR}/*.proto"
)
set(PROTO_PATH "${CMAKE_CURRENT_SOURCE_DIR}")
set(PROTO_C_OUT "${CMAKE_CURRENT_BINARY_DIR}/gen_c")
set(PROTO_CXX_OUT "${CMAKE_CURRENT_BINARY_DIR}/gen_cxx")
file(MAKE_DIRECTORY ${PROTO_C_OUT})
file(MAKE_DIRECTORY ${PROTO_CXX_OUT})
# 使用 protoc 处理 proto 文件
foreach(input_proto ${PROTO_SOURCE_FILES})
get_filename_component(DIR ${input_proto} DIRECTORY)
get_filename_component(FILE_NAME ${input_proto} NAME_WE)
set(OUTPUT_C_HEADER "${PROTO_C_OUT}/${FILE_NAME}.pb-c.h")
set(OUTPUT_C_SOURCE "${PROTO_C_OUT}/${FILE_NAME}.pb-c.c")
list(APPEND OUTPUT_SOURCES_C
${OUTPUT_C_HEADER} ${OUTPUT_C_SOURCE})
set(OUTPUT_CXX_HEADER "${PROTO_CXX_OUT}/${FILE_NAME}.pb.h")
set(OUTPUT_CXX_SOURCE "${PROTO_CXX_OUT}/${FILE_NAME}.pb.cc")
list(APPEND OUTPUT_SOURCES_CXX
${OUTPUT_CXX_HEADER} ${OUTPUT_CXX_SOURCE})
endforeach()
add_custom_command(
OUTPUT ${OUTPUT_SOURCES_C}
COMMAND ${PROTOC_C} --c_out=${PROTO_C_OUT} --proto_path=${PROTO_PATH} ${PROTO_SOURCE_FILES}
DEPENDS ${PROTO_SOURCE_FILES}
WORKING_DIRECTORY ${PROTO_PATH}
COMMENT "Generate C Protobuf Source Files"
)1
add_custom_command(
OUTPUT ${OUTPUT_SOURCES_CXX}
COMMAND ${PROTOC_CXX} --cpp_out=${PROTO_CXX_OUT} --proto_path=${PROTO_PATH} ${PROTO_SOURCE_FILES}
DEPENDS ${PROTO_SOURCE_FILES}
WORKING_DIRECTORY ${PROTO_PATH}
COMMENT "Generate Cpp Protobuf Source Files"
)
add_custom_target(
compile_c_protos
DEPENDS ${OUTPUT_SOURCES_C}
)
add_custom_target(
compile_cxx_protos
DEPENDS ${OUTPUT_SOURCES_CXX}
)
# 设置生成源文件包含目录变量供上层引用
set(PROTO_GEN_C_INCLUDE_DIRS ${PROTO_C_OUT} PARENT_SCOPE)
set(PROTO_GEN_CXX_INCLUDE_DIRS ${PROTO_CXX_OUT} PARENT_SCOPE)
# 将生成的文件打包为库 proto_gen_c
# 程序可以链接到该库
add_library(proto_gen_c ${OUTPUT_SOURCES_C})
target_link_libraries(proto_gen_c protobuf-c)
add_dependencies(proto_gen_c compile_c_protos)
add_library(proto_gen_cxx ${OUTPUT_SOURCES_CXX})
target_link_libraries(proto_gen_cxx protobuf)
add_dependencies(proto_gen_cxx compile_cxx_protos)
|
在项目主 CMakeLists.txt
中,如下引用:
1
2
3
|
add_subdirectory(proto)
include_directories(${PROTO_GEN_C_INCLUDE_DIRS})
include_directories(${PROTO_GEN_CXX_INCLUDE_DIRS})
|
在需要使用 proto 的目标 CMakeLists.txt
脚本中,只需要目标链接 proto_gen_cxx
或 proto_gen_c
即可。