Search code examples
c++cmakeprotocol-buffers

How to build a cmake project which uses protobuf?


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;
}

Error

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.


Solution

  • 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)