Generate gRPC source files using CMake
When working with Protobuf schema files you will eventually get to a point where you need to turn them into source code. Protobuf provides a CMake module to do just that, it merely seems to lack all documentation (EDIT: My documentation has since been merged upstream). In this article I describe how to generate Protobuf and gRPC source code from schema files at build time. Here is the complete code:
Given the following directory structure:
We start by calling find_package(protobuf)
which has to be done in the same directory that uses protobuf_generate
or any parent directory. Next we add our proto schema files to the sources of a CMake target, here we create a new target but it could also be an existing one. Alternatively, we can also provide the path to our proto schema files through the PROTOS
argument to protobuf_generate
. Since the generated source files will be compiled when we build our target, we also need to link with the Protobuf and gRPC libraries using their imported target names protobuf::libprotobuf
and gRPC::grpc++
respectively.
What follows is the actual call to protobuf_generate
. It sets up add_custom_command
for protoc (the Protobuf compiler) that causes automatic re-generation of the Protobuf and gRPC source files whenever we make changes to their proto schemas. protobuf_generate
accepts the following arguments:
APPEND_PATH
— A flag that causes the base path of all proto schema files to be added toIMPORT_DIRS
.LANGUAGE
— A single value: cpp or python. Determines what kind of source files are being generated.OUT_VAR
— Name of a CMake variable that will be filled with the paths to the generated source files.EXPORT_MACRO
— Name of a macro that is applied to all generated Protobuf message classes andextern
variables.PROTOC_OUT_DIR
—Output directory of the generated source files, defaults toCMAKE_CURRENT_BINARY_DIR
.PLUGIN
— An optional plugin executable. This could e.g. be the path togrpc_cpp_plugin
.PLUGIN_OPTIONS
— Additional options provided to the plugin, e.g.generate_mock_code=true
for the gRPC cpp plugin.TARGET
— Generated files will be added as sources to the provided target.PROTOS
— List of proto schema files. If omitted, then every source file ending inproto
ofTARGET
will be used.IMPORT_DIRS
— A common parent directory for the schema files. E.g., if the schema file isproto/helloworld/helloworld.proto
and the import directoryproto/
then the generated file is${PROTOC_OUT_DIR}/helloworld/helloworld.pb.cc
.GENERATE_EXTENSIONS
— IfLANGUAGE
is omitted then this must be set to the extensions thatprotoc
generates.PROTOC_OPTIONS
— Additional arguments that are forwarded to theprotoc
invocation.
After invoking protobuf_generate
we are left with adding PROTOC_OUT_DIR
to the include directories of our target and disabling unity build. Within the generated .cc
files, Protobuf creates static variables at namespace-scope whose names are not unique and are therefore unsuitable for unity builds.
Actually, there is one more thing. If you are using cmake-format (highly recommended) and want to get the same formatting as in the code above then add the following entry to the additional_commands section of the cmake-format.yaml in the root directory of your project:
If you also think these calls to protobuf_generate
are too verbose and hard to remember then take a look at asio-grpc. It not only provides a convenient Asio/std::execution-based API for writing asynchronous gRPC clients and servers but also the CMake function asio_grpc_protobuf_generate
that combines the generation of Protobuf and gRPC source files into a single call:
Special thanks to Falko Axmann for contributing the PLUGIN
support to protobuf_generate
and his article on its usage.