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.

Finding the right version of protobuf_generate

Updated/Added on 2021-09-05

Depending on your version of cmake and how you installed gRPC, you might get the following build error:

protoc-gen-grpc: program not found or is not executable
Please specify a program using absolute path or make sure the program is available in your PATH system variable
--grpc_out: protoc-gen-grpc: Plugin failed with status code 1.

Problem

So why is this happening? The reason is that there could be two versions of the protobuf cmake script installed, which is the script that provides the protobuf_generate command. One installed by cmake, the other installed as part of the protobuf install.

The versions provided by cmake can lag behind (sometimes severely) the ones that come with the libraries and so, if your cmake version isn’t up-to-date, the protobuf_generate command might not yet support the --plugin option, leading to the error message you see.

This might come as a surprise, since we explicitly use find_package(protobuf CONFIG REQUIRED) to find the protobuf package. With the CONFIG option, we are explicitly requesting cmake to prefer the ProtobufConfig.cmake that comes with protobuf, over its own (outdated) FindProtobuf.cmake module. Unfortunately, this gets completely ignored by cmake, because gRPC tries to be helpful by finding all its dependencies for us, thus our find_package for protobuf never gets executed as gRPC has already found the outdated one.

Solution

There are a couple of options:

Update cmake

The newer version has the improved protobuf_generate function in it, though at the time of this writing it is already lagging behind the upstream ProtobufConfig.cmake in other regards.

Build and install gRPC yourself

Use the following options when running cmake:

-DgRPC_PROTOBUF_PROVIDER=package
-DgRPC_PROTOBUF_PACKAGE_TYPE=CONFIG

This will instruct gRPC to find its dependencies via CONFIG mode.

Make CONFIG mode the default

When running cmake for this example, use the -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=TRUE option to make CONFIG mode the default mode. You then don’t have to change the way gRPC is installed. See the cmake docs for more information.

Resources