Search code examples
c++cmakeprotocol-buffersconan

How to properly install and link Protobuf with conan2 + cmake when system protoc exists


This example project reflects my issue. Project consists of conanfile for handling dependencies and CMakeLists in root directory for setting up some language specific constants. Source directory has main.cpp with generic Hello, world! output, that is first moved into sample protobuf message and then extracted. Also source directory has CMakeLists for handling build and hello-world.proto message. Setup:

├── CMakeLists.txt
├── conanfile.py
└── src
    ├── CMakeLists.txt
    ├── main.cpp
    └── protos
        ├── CMakeLists.txt
        └── hello-world.proto

conanfile.py:

from conan import ConanFile

class SoundServer(ConanFile):
    settings = ["os", "compiler", "arch", "build_type"]
    generators = ["CMakeToolchain", "CMakeDeps"]
    requires = ["protobuf/3.21.12"]

CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)

project(example)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_EXTENSIONS ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# set(CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH FALSE)
# set(CMAKE_FIND_USE_CMAKE_SYSTEM_PATH FALSE)

# packages
find_package(Protobuf CONFIG REQUIRED VERSION 3.21.12)

add_subdirectory(src)

src/CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)

project(some-module)

add_subdirectory(protos)

add_executable(pulse-calls-mapper main.cpp)

target_link_libraries(pulse-calls-mapper PRIVATE protos)

src/main.cpp:

#include <iostream>

// Protos
#include <hello-world.pb.h>

int main() {
    PHelloWorld message;
    message.set_data("Hello, world!\n");
    std::cout << message.data();

    return 0;
}

src/protos/CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)

project(protos)

protobuf_generate_cpp(SOURCES HEADERS hello-world.proto)

add_library(protos STATIC ${SOURCES} ${HEADERS})

target_include_directories(protos PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(protos PRIVATE ${protobuf_LIBRARIES})

and finally proto message:

syntax = "proto3";

message PHelloWorld {
    string data = 1;
}

Important: I have protobuf package installed in my system as dependency of some other packages:

$ protoc --version   
libprotoc 25.2

My problem is mismatch of versions between protoc compiler and target that conan provides me with. To build all of this, I use:

mkdir build && cd build
conan install .. --output-folder="." --build=missing
cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE="../build/conan_toolchain.cmake"
cmake --build . -j8

Version mismatch:

build/src/protos/hello-world.pb.h:15:2: error: #error "This file was generated by a newer version of protoc which is"
   15 | #error "This file was generated by a newer version of protoc which is"
      |  ^~~~~
build/src/protos/hello-world.pb.h:16:2: error: #error "incompatible with your Protocol Buffer headers. Please update"
   16 | #error "incompatible with your Protocol Buffer headers. Please update"
      |  ^~~~~
build/src/protos/hello-world.pb.h:17:2: error: #error "your headers."
   17 | #error "your headers."
      |  ^~~~~

It seems that my system protoc is newer (4.25.2) than the package (3.21.12)

I looked into the sources and found out that protobuf-module.cmake uses find_program() to locate protoc executable and I thought it is a viable idea to disable system paths for that with:

set(CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH FALSE)
set(CMAKE_FIND_USE_CMAKE_SYSTEM_PATH FALSE)

(commented lines in my root CMakeLists)

After that build fails with the same error, but now version of protoc is older that protobuf headers. What can I do to solve this?

EDIT 1: Thanks to comments, I included in conanfile.py

tool_requires = ["protobuf/3.21.12"]

Problem with version conflict is resolved if I set include path to include/ in conan prefix this way src/protos/CMakeLists.txt:

target_include_directories(protos PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${protobuf_INCLUDE_DIR})

So version defined in macro will actually match conan's protoc and not my system-wide available one. But I still get linker errors trying to link to any combination of protobuf::protobuf, protobuf::libprotobuf or protobuf::protoc.

EDIT 2: Linking directly (with absolute path) to libprotopuf.a in conan prefix solves the issue.

EDIT: 3: I checked my conan profile and found that I build for package s for Release, but my project build was for Debug. Now with config match I was able to properly link to protonuf::libprotobuf.


Solution

  • I was able to solve this issue with two main steps:

    • add tool_requires to conanfile
    • set include dirs for proto target

    Setting include dirs is important because when cmake builds your target with actual proto files it checks version match with macro in special header. If you have protobuf installed in your system, then #include resolves to system path and versions are likely to conflict. That is why it is important to provide correct include with protobuf_INCLUDE_DIR(S) variable.

    Thanks @uilianries for the hint.

    Also always make sure your conan profile has compatible settings with your build options for the project! :)