[C++] Protobuf: Project Setup With Conan and CMake
In my last article I wrote about Conan, a C/C++ package manager. In this article we'll continue using Conan and start a simple project with Google Protocolbuffers.
Please note: This post is about setting up a clean protobuffer project with Conan and CMake.
Before we go through this project, we'll add Bincrafters to the conan remotes to download Google Protocolbuffers. The project structure will look like the following tree (without the build directory):
.
├── CMakeLists.txt
├── conanfile.txt
├── proto
│ └── Animal.proto
└── src
└── main.cpp
Bincrafters
Bincrafters is another remote, where we access Google Protocolbuffers. Run the following two commands to add this remote. We need to set revisions to 1, refer to this article for further information.
conan remote add bincrafters https://bincrafters.jfrog.io/artifactory/api/conan/public-conan
conan config set general.revisions_enabled=1
conanfile.txt
We need two dependencies,
- the protobuffer compiler to create the generic classes which we use in our C++ code and
- the protobuffer libraries to link our code.
The conanfile.txt
contains then:
[requires]
protobuf/3.9.1@bincrafters/stable
protoc_installer/3.9.1@bincrafters/stable
[generators]
cmake
Now we can run conan to download the dependencies and create the conanbuildinfo.cmake
. Create a build directory and run conan (and build all missing packages).
mkdir build
cd build
conan install .. --build missing
proto/Animal.proto
This defines the proto message, which we want to use. We simply define an animal with three fields: Species, name and age. The protobuffer compiler will create an animal class which we'll use in src/main.cpp
. We are using latest syntax version 3, for more information and a syntax guide visit the developers.google
// ./proto/Animal.proto
// protobuffer framework will generate the classes which we'll access later
syntax = "proto3";
message Animal {
string species = 1;
string name = 2;
int32 age = 3;
}
CMakeLists.txt
At first we need to include the conanbuildinfo.cmake
and run conan_basic_setup()
. Then we can find the protobuffer package. By adding the Animal.proto
to the target executable, we can run the protobuf_generate macro to create the generic animal class.
cmake_minimum_required(VERSION 3.16)
project(Protobuffer)
set(target ProtobufferExample)
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
# we'll find the protobuffer dependency which was downloaded with conan
find_package(Protobuf REQUIRED)
# add the Animal.proto here to create the generic classes with protobuf_generate(...)
add_executable(${target}
${PROJECT_SOURCE_DIR}/proto/Animal.proto
${PROJECT_SOURCE_DIR}/src/main.cpp
)
protobuf_generate(TARGET ${target}
PROTOS ${PROJECT_SOURCE_DIR}/proto/Animal.proto
)
target_link_libraries(${target}
${CONAN_LIBS}
)
# the output directory for the proto compiler is our build directory
# if we want to access the generic animal header we need to include this directory
target_include_directories(${target} PUBLIC
${CMAKE_CURRENT_BINARY_DIR}
)
Now run the CMake configure command (make sure that the current working directory is still ./build
):
cmake -S ./.. -B ./
The created animal class is in the build directory.
.
├── build
│ └── proto
│ ├── Animal.pb.cc
│ └── Animal.pb.h
src/main.cpp
We just craete one Animal
and call their setters and getters to demonstrate that the project was successful built and we can use protobuf library.
#include <iostream>
#include "proto/Animal.pb.h"
int main(int argc, char* argv[])
{
Animal message;
message.set_species("Cat");
message.set_name("Tiffany");
message.set_age(12);
std::cout << "created proto message with animal:\n"
<< "species: " << message.species() << '\n'
<< "name: " << message.name() << '\n'
<< "age: " << message.age() << '\n';
return 0;
}
Build And Run
Now use CMake to finally build the project and execute it. We are now able to use google protocolbuffers in this project.
cmake --build ./
./bin/ProtobufferExample
Conclusion
Once more Conan has proven to be a good solution for managing dependencies. A few advantages for me are:
- Conan downloads and builds protobuf and the proto compiler
- The proto compiler is executed by cmake
- A simple
CMakeLists.txt
with just a few lines of code
The project is on GitHub. Feel free to play around.
That's it for now.
Best Thomas
Edit: Remove find_package(..) From CMakeLists.txt
We can get rid of the find_package(Protobuf REQUIRED)
macro in the CMakeLists.txt
, by adding cmake_paths
to the [generators]
in the conanfile.txt
.
[requires]
protobuf/3.9.1@bincrafters/stable
protoc_installer/3.9.1@bincrafters/stable
[generators]
cmake
cmake_paths
And then we include the new created conan_paths.cmake
and protoc-config.cmake
in the CMakeLists.txt
. As result we can drop the find_package(Protobuf REQUIRED)
command.
cmake_minimum_required(VERSION 3.16)
project(Protobuffer)
set(target ProtobufferExample)
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
# EDIT: after adding cmake_paths in conanfile.txt we can include conan_paths.cmake
include(${CMAKE_BINARY_DIR}/conan_paths.cmake)
conan_basic_setup()
# find_package(Protobuf REQUIRED)
# EDIT: and we can get rid of the find_package command by including protoc-config.cmake
include(${CONAN_PROTOC_INSTALLER_ROOT}/lib/cmake/protoc/protoc-config.cmake)
add_executable(${target}
${PROJECT_SOURCE_DIR}/proto/Animal.proto
${PROJECT_SOURCE_DIR}/src/main.cpp
)
protobuf_generate(TARGET ${target}
PROTOS ${PROJECT_SOURCE_DIR}/proto/Animal.proto
)
target_link_libraries(${target}
${CONAN_LIBS}
)
target_include_directories(${target} PUBLIC
${CMAKE_CURRENT_BINARY_DIR}
)
Edit 2022:
As Tom from the comments pointed out, protobuf_generate
stopped working. I noticed this as well a while ago and as quick fix you can simply use cmake's execute_process
to use the proto compiler. You also can create a cmake function/macro to use it.
# unfortunately the directory will not be created by protoc
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/proto)
# execute protoc from the conan directory
# pass the filename of the proto file
# I use by default the build directory to put the generated files in
# and set the working directory where your proto file is located
execute_process(
COMMAND
"${CONAN_BIN_DIRS_PROTOBUF}/protoc"
"my_protofile.proto"
"--cpp_out=${CMAKE_BINARY_DIR}/proto"
WORKING_DIRECTORY "${DIRECTORY WHERE my_protofile.proto IS LOCATED}"
)
add_executable(${your target}
${CMAKE_BINARY_DIR}/proto/my_protofile.pb.cc
# ...
)
# ...
# later on don't forget to set the include directory to use your proto class
target_include_directories(${target} PUBLIC
${CMAKE_BINARY_DIR}/proto
# ...
)