A while back, I contributed plugin/grpc support to the CMake package that comes with protobuf. Now that I found the time, I want to write a bit about how to use this in your project to automatically generate C++ code from your protobuf files.

The full example is available in my github account: gRPC CMake example

Why?

When you build gRPC clients or servers, you will specify their API in .proto files. These specify not only the format of the protobuf messages that get sent by gRPC, but also the services (calls) provided by the server.

Every time you update the .proto files, you will have to run the protoc compiler to convert them to C++ code. Actually you have to run protoc twice: Once to generate the C++ files for the protobuf serialization and another time with the gRPC plugin to generate the server/client code.

Doing this manually is error-prone and should be avoided. This post describes how to add the .proto files to your CMake project to automate these steps.

Structure of the project

When you look at the example project, you will see that it is split into three parts:

  • A client,
  • a server,
  • and the proto files.

Since the protobuf/gRPC code is shared among the client and the server, I decided to put the code into a CMake library target and then link the client and server to that library. This also helps with keeping all the dependencies in one place.

In the following I will only focus on the CMakeLists.txt file in the proto/ directory.

Compiling the proto files into a library

For most of the time, you can just pretend that the .proto files are c++ files and create a library target with them as source files:

set(PROTO_FILES
    myproto/address.proto
    myproto/addressbook.proto
)

# Add Library target with protobuf sources
add_library(myproto ${PROTO_FILES})
target_link_libraries(myproto
    PUBLIC
        protobuf::libprotobuf
        gRPC::grpc
        gRPC::grpc++
)
target_include_directories(myproto PUBLIC ${CMAKE_CURRENT_BINARY_DIR})

The magic happens at the very end of CMakeLists.txt where the helper function protobuf_generate is called. In its basic form it takes care of compiling all message types in the .proto files to C++:

protobuf_generate(TARGET myproto LANGUAGE cpp)

This, however, will not create the gRPC client/server code, as that requires the grpc plugin. To use it, we first have to find the plugin that was installed with gRPC:

get_target_property(grpc_cpp_plugin_location gRPC::grpc_cpp_plugin LOCATION)

and once we have the location of the plugin we can tell protoc to run it by invoking protobuf_generate together with the PLUGIN option:

protobuf_generate(TARGET myproto LANGUAGE grpc GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc PLUGIN "protoc-gen-grpc=${grpc_cpp_plugin_location}")

Note that we specified a TARGET for protobuf_generate. This will convert all .proto files of that target to C++ files and will add them automatically to that target, so that they are compiled into the library.

Resources