I'm trying to create a small C++ test project which uses protobuf and the cmake build system.
I managed to get everything working with a single directory, and a single CMakeLists.txt
.
However, this isn't a scalable structure.
The next change I attempted was to create a proto
directory, and to move the *.proto
files into this directory.
The project no longer builds, and I cannot figure out how to fix it.
I have searched the web for solutions, and also tried asking ChatGPT. ChatGPT went around in circles, and I found what appeared to be very variable solutions by searching the limited resources I could find online. It was not obvious to me which of the many variations might be the right way to go, but this is most likely because I am not an expert with cmake
, so couldn't figure out how to put the various pieces together.
This is what I currently have:
protobuf-example/
proto/
CMakeLists.txt
message.proto
CMakeLists.txt
main.cpp
proto/CMakeLists.txt
I am not totally sure what from the below is required. I have some understanding of what each of these lines does, but my understanding is not very solid.
set(PROTO_FILES message.proto)
set(GENERATED_PROTO_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated)
file(MAKE_DIRECTORY ${GENERATED_PROTO_DIR})
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${PROTO_FILES})
add_library(proto_files STATIC ${PROTO_SRCS})
target_include_directories(proto_files PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(proto_files PUBLIC ${Protobuf_LIBRARIES})
set(PROTO_GEN_SRCS ${PROTO_SRCS} PARENT_SCOPE)
set(PROTO_GEN_HDRS ${PROTO_HDRS} PARENT_SCOPE)
set(PROTO_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR} PARENT_SCOPE)
message.proto
syntax = "proto3";
message Person {
string name = 1;
int32 age = 2;
string email = 3;
}
CMakeLists.txt
On the other hand, I am familiar with these statements and I am fairly sure I know what each of them does.
cmake_minimum_required(VERSION 3.10)
project(ProtobufExample LANGUAGES CXX)
find_package(Protobuf REQUIRED)
add_subdirectory(proto)
add_executable(protobuf_example main.cpp ${PROTO_GEN_SRCS})
target_include_directories(protobuf_example PRIVATE ${Protobuf_INCLUDE_DIR})
target_link_libraries(protobuf_example PRIVATE ${proto_files})
main.cpp
As far as I am aware, this is just a standard example.
#include <iostream>
#include <fstream>
#include "message.pb.h"
void serializePerson(const std::string& filename) {
Person person;
person.set_name("John Doe");
person.set_age(30);
person.set_email("[email protected]");
std::ofstream output(filename, std::ios::binary);
if (!person.SerializeToOstream(&output)) {
std::cerr << "Failed to serialize data." << std::endl;
}
}
void deserializePerson(const std::string& filename) {
Person person;
std::ifstream input(filename, std::ios::binary);
if (!person.ParseFromIstream(&input)) {
std::cerr << "Failed to parse data." << std::endl;
} else {
std::cout << "Name: " << person.name() << "\n"
<< "Age: " << person.age() << "\n"
<< "Email: " << person.email() << std::endl;
}
}
int main() {
GOOGLE_PROTOBUF_VERIFY_VERSION;
const std::string filename = "person.data";
serializePerson(filename);
deserializePerson(filename);
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
The specific error message is:
Cannot find source file:
/home/user/cmake-protobuf-test/protobuf-example/build/proto/message.pb.cc
This is most likely because the build/proto/generated
directory is empty.
So it seems as if the protobuf_generate_cpp
is not doing anything. However there are no errors or warnings produced relating to this.
Two issues are there. The first one:
add_executable(protobuf_example main.cpp ${PROTO_GEN_SRCS})
must be
add_executable(protobuf_example main.cpp)
because ${PROTO_GEN_SRCS}
are sources of proto_files
.
And the typo:
target_link_libraries(protobuf_example PRIVATE ${proto_files})
must be
target_link_libraries(protobuf_example PRIVATE proto_files)
because targets are not variables.
A nitpick. This is unnecessary
target_include_directories(protobuf_example PRIVATE ${Protobuf_INCLUDE_DIR})
because the further target_link_libraries
brings #include to protobuf_example
.
This results in that this is also unnecessary
set(PROTO_GEN_SRCS ${PROTO_SRCS} PARENT_SCOPE)
set(PROTO_GEN_HDRS ${PROTO_HDRS} PARENT_SCOPE)
set(PROTO_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR} PARENT_SCOPE)