From 80f79d36805aef0d2645d7ee606e754bb7e9b609 Mon Sep 17 00:00:00 2001 From: Carles Fernandez Date: Tue, 4 Aug 2026 10:44:44 +0200 Subject: [PATCH] CMake: improve Protobuf handling --- CMakeLists.txt | 202 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 154 insertions(+), 48 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2650e41f6..250a7d774 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2301,41 +2301,14 @@ else() endif() if(NOT Protobuf_FOUND) + set(GNSSSDR_PROTOBUF_MODULE_MODE TRUE) find_package(Protobuf MODULE) - - if(Protobuf_FOUND) - # FindProtobuf reports the C++ runtime version, while protobuf.pc - # uses the release version. For example, "4.25.3" maps to "25.3.0". - string(REGEX REPLACE "^[0-9]+\.([0-9]+\.[0-9]+)$" "\\1.0" - PROTOBUF_LIBRARY_VERSION "${Protobuf_VERSION}") - if(NOT (PROTOBUF_LIBRARY_VERSION VERSION_LESS "22")) - if(CMAKE_VERSION VERSION_LESS 3.6) - pkg_check_modules(GNSSSDR_PROTOBUF_PC QUIET - protobuf=${PROTOBUF_LIBRARY_VERSION}) - else() - pkg_check_modules(GNSSSDR_PROTOBUF_PC QUIET IMPORTED_TARGET - protobuf=${PROTOBUF_LIBRARY_VERSION}) - endif() - if(NOT GNSSSDR_PROTOBUF_PC_FOUND) - message(FATAL_ERROR - "Protocol Buffers ${Protobuf_VERSION} was found through CMake's FindProtobuf module, " - "but matching pkg-config metadata for protobuf ${PROTOBUF_LIBRARY_VERSION} was not found. " - "Make protobuf-config.cmake discoverable or install the matching protobuf.pc file so " - "that Protobuf's Abseil and utf8_range dependencies can be linked correctly." - ) - endif() - endif() - endif() endif() endif() -set_package_properties(Protobuf PROPERTIES - URL "https://protobuf.dev/" - PURPOSE "Used to serialize output data in a way that can be read by other applications." - TYPE REQUIRED -) -if(NOT Protobuf_VERSION) - set(Protobuf_VERSION "0.0.0") -endif() + +# FindProtobuf used only upper-case output variables before CMake 3.6 and did +# not provide a version before CMake 3.6. Normalize those results before using +# them to decide whether modern Protobuf dependencies are needed. if(CMAKE_VERSION VERSION_LESS 3.6) if(PROTOBUF_FOUND) set(Protobuf_FOUND ${PROTOBUF_FOUND}) @@ -2345,6 +2318,11 @@ if(Protobuf_FOUND AND CMAKE_VERSION VERSION_LESS 3.9) if(PROTOBUF_INCLUDE_DIR) set(Protobuf_INCLUDE_DIR ${PROTOBUF_INCLUDE_DIR}) endif() + if(PROTOBUF_INCLUDE_DIRS) + set(Protobuf_INCLUDE_DIRS ${PROTOBUF_INCLUDE_DIRS}) + elseif(PROTOBUF_INCLUDE_DIR) + set(Protobuf_INCLUDE_DIRS ${PROTOBUF_INCLUDE_DIR}) + endif() if(PROTOBUF_LIBRARY) set(Protobuf_LIBRARY ${PROTOBUF_LIBRARY}) endif() @@ -2352,8 +2330,123 @@ if(Protobuf_FOUND AND CMAKE_VERSION VERSION_LESS 3.9) set(Protobuf_PROTOC_EXECUTABLE ${PROTOBUF_PROTOC_EXECUTABLE}) endif() if(PROTOBUF_PROTOC_LIBRARY) - set(Protobuf_PROTOC_LIBRARY ${PROTOBUF_PROTOC_EXECUTABLE}) + set(Protobuf_PROTOC_LIBRARY ${PROTOBUF_PROTOC_LIBRARY}) endif() +endif() +if(Protobuf_FOUND AND NOT Protobuf_VERSION) + set(_PROTOBUF_COMMON_HEADER ${Protobuf_INCLUDE_DIR}/google/protobuf/stubs/common.h) + if(EXISTS "${_PROTOBUF_COMMON_HEADER}") + file(STRINGS ${_PROTOBUF_COMMON_HEADER} _PROTOBUF_COMMON_H_CONTENTS + REGEX "#define[ \t]+GOOGLE_PROTOBUF_VERSION[ \t]+[0-9]+") + if(_PROTOBUF_COMMON_H_CONTENTS MATCHES + "#define[ \t]+GOOGLE_PROTOBUF_VERSION[ \t]+([0-9]+)") + set(Protobuf_LIB_VERSION "${CMAKE_MATCH_1}") + math(EXPR _PROTOBUF_MAJOR_VERSION "${Protobuf_LIB_VERSION} / 1000000") + math(EXPR _PROTOBUF_MINOR_VERSION "${Protobuf_LIB_VERSION} / 1000 % 1000") + math(EXPR _PROTOBUF_SUBMINOR_VERSION "${Protobuf_LIB_VERSION} % 1000") + set(Protobuf_VERSION + "${_PROTOBUF_MAJOR_VERSION}.${_PROTOBUF_MINOR_VERSION}.${_PROTOBUF_SUBMINOR_VERSION}") + endif() + unset(_PROTOBUF_COMMON_H_CONTENTS) + endif() + unset(_PROTOBUF_COMMON_HEADER) +endif() +if(NOT Protobuf_VERSION) + set(Protobuf_VERSION "0.0.0") +endif() + +if(GNSSSDR_PROTOBUF_MODULE_MODE AND Protobuf_FOUND) + # FindProtobuf reports the C++ runtime version, while protobuf.pc uses the + # release version. For example, "7.34.2" maps to "34.2.0". + string(REGEX REPLACE "^[0-9]+[.]([0-9]+[.][0-9]+)$" "\\1.0" + PROTOBUF_LIBRARY_VERSION "${Protobuf_VERSION}") + if(NOT (PROTOBUF_LIBRARY_VERSION VERSION_LESS "22")) + if(CMAKE_VERSION VERSION_LESS 3.6) + pkg_check_modules(GNSSSDR_PROTOBUF_PC QUIET + protobuf=${PROTOBUF_LIBRARY_VERSION}) + else() + pkg_check_modules(GNSSSDR_PROTOBUF_PC QUIET IMPORTED_TARGET + protobuf=${PROTOBUF_LIBRARY_VERSION}) + endif() + if(NOT GNSSSDR_PROTOBUF_PC_FOUND) + message(FATAL_ERROR + "Protocol Buffers ${Protobuf_VERSION} was found through CMake's FindProtobuf module, " + "but matching pkg-config metadata for protobuf ${PROTOBUF_LIBRARY_VERSION} was not found. " + "Make protobuf-config.cmake discoverable or install the matching protobuf.pc file so " + "that Protobuf's Abseil and utf8_range dependencies can be linked correctly." + ) + endif() + + # protobuf.pc exposes the link dependencies, but some distributions do + # not publish the C++ standard used to build Abseil. Probe the installed + # headers and propagate the lowest standard that is actually accepted. + include(CheckCXXSourceCompiles) + set(_GNSSSDR_REQUIRED_FLAGS_SAVED "${CMAKE_REQUIRED_FLAGS}") + set(_GNSSSDR_REQUIRED_INCLUDES_SAVED "${CMAKE_REQUIRED_INCLUDES}") + string(REPLACE ";" " " _GNSSSDR_PROTOBUF_PC_CFLAGS + "${GNSSSDR_PROTOBUF_PC_CFLAGS_OTHER}") + set(CMAKE_REQUIRED_INCLUDES + "${Protobuf_INCLUDE_DIRS};${GNSSSDR_PROTOBUF_PC_INCLUDE_DIRS}") + set(_GNSSSDR_PROTOBUF_STANDARD_PROBE + "#include \nint main() { return 0; }\n") + set(_GNSSSDR_PROTOBUF_MINIMUM_CXX_STANDARD 14) + if(NOT (PROTOBUF_LIBRARY_VERSION VERSION_LESS "30")) + set(_GNSSSDR_PROTOBUF_MINIMUM_CXX_STANDARD 17) + endif() + unset(GNSSSDR_PROTOBUF_REQUIRED_CXX_STANDARD) + foreach(_GNSSSDR_CXX_STANDARD 14 17 20) + if(NOT (_GNSSSDR_CXX_STANDARD VERSION_LESS + _GNSSSDR_PROTOBUF_MINIMUM_CXX_STANDARD)) + if(MSVC) + if(_GNSSSDR_CXX_STANDARD VERSION_LESS "17") + set(_GNSSSDR_CXX_STANDARD_OPTION "") + else() + set(_GNSSSDR_CXX_STANDARD_OPTION + "/std:c++${_GNSSSDR_CXX_STANDARD}") + endif() + else() + set(_GNSSSDR_CXX_STANDARD_OPTION + "-std=c++${_GNSSSDR_CXX_STANDARD}") + endif() + set(CMAKE_REQUIRED_FLAGS + "${_GNSSSDR_REQUIRED_FLAGS_SAVED} ${_GNSSSDR_PROTOBUF_PC_CFLAGS} ${_GNSSSDR_CXX_STANDARD_OPTION}") + set(_GNSSSDR_PROTOBUF_STANDARD_TEST + "GNSSSDR_PROTOBUF_CXX${_GNSSSDR_CXX_STANDARD}_WORKS") + unset(${_GNSSSDR_PROTOBUF_STANDARD_TEST} CACHE) + check_cxx_source_compiles( + "${_GNSSSDR_PROTOBUF_STANDARD_PROBE}" + ${_GNSSSDR_PROTOBUF_STANDARD_TEST}) + if(${_GNSSSDR_PROTOBUF_STANDARD_TEST}) + set(GNSSSDR_PROTOBUF_REQUIRED_CXX_STANDARD + ${_GNSSSDR_CXX_STANDARD}) + break() + endif() + endif() + endforeach() + set(CMAKE_REQUIRED_FLAGS "${_GNSSSDR_REQUIRED_FLAGS_SAVED}") + set(CMAKE_REQUIRED_INCLUDES "${_GNSSSDR_REQUIRED_INCLUDES_SAVED}") + if(NOT GNSSSDR_PROTOBUF_REQUIRED_CXX_STANDARD) + message(FATAL_ERROR + "Protocol Buffers ${Protobuf_VERSION} headers do not compile with any C++ standard " + "supported by GNSS-SDR (up to C++20). Install a compatible Protobuf and Abseil pair." + ) + endif() + unset(_GNSSSDR_CXX_STANDARD) + unset(_GNSSSDR_CXX_STANDARD_OPTION) + unset(_GNSSSDR_PROTOBUF_MINIMUM_CXX_STANDARD) + unset(_GNSSSDR_PROTOBUF_PC_CFLAGS) + unset(_GNSSSDR_PROTOBUF_STANDARD_PROBE) + unset(_GNSSSDR_PROTOBUF_STANDARD_TEST) + unset(_GNSSSDR_REQUIRED_FLAGS_SAVED) + unset(_GNSSSDR_REQUIRED_INCLUDES_SAVED) + endif() +endif() +set_package_properties(Protobuf PROPERTIES + URL "https://protobuf.dev/" + PURPOSE "Used to serialize output data in a way that can be read by other applications." + TYPE REQUIRED +) +if(Protobuf_FOUND AND CMAKE_VERSION VERSION_LESS 3.9) add_library(protobuf::libprotobuf SHARED IMPORTED) set_target_properties(protobuf::libprotobuf PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" @@ -2367,23 +2460,9 @@ if(Protobuf_FOUND AND CMAKE_VERSION VERSION_LESS 3.9) IMPORTED_LOCATION "${Protobuf_PROTOC_EXECUTABLE}" INTERFACE_LINK_LIBRARIES "${Protobuf_PROTOC_LIBRARY}" ) - if(${Protobuf_VERSION} VERSION_EQUAL "0.0.0") - set(_PROTOBUF_COMMON_HEADER ${Protobuf_INCLUDE_DIR}/google/protobuf/stubs/common.h) - set(Protobuf_VERSION "") - set(Protobuf_LIB_VERSION "") - file(STRINGS ${_PROTOBUF_COMMON_HEADER} _PROTOBUF_COMMON_H_CONTENTS REGEX "#define[ \t]+GOOGLE_PROTOBUF_VERSION[ \t]+") - if(_PROTOBUF_COMMON_H_CONTENTS MATCHES "#define[ \t]+GOOGLE_PROTOBUF_VERSION[ \t]+([0-9]+)") - set(Protobuf_LIB_VERSION "${CMAKE_MATCH_1}") - endif() - unset(_PROTOBUF_COMMON_H_CONTENTS) - math(EXPR _PROTOBUF_MAJOR_VERSION "${Protobuf_LIB_VERSION} / 1000000") - math(EXPR _PROTOBUF_MINOR_VERSION "${Protobuf_LIB_VERSION} / 1000 % 1000") - math(EXPR _PROTOBUF_SUBMINOR_VERSION "${Protobuf_LIB_VERSION} % 1000") - set(Protobuf_VERSION "${_PROTOBUF_MAJOR_VERSION}.${_PROTOBUF_MINOR_VERSION}.${_PROTOBUF_SUBMINOR_VERSION}") - endif() endif() -if(GNSSSDR_PROTOBUF_PC_FOUND) +if(GNSSSDR_PROTOBUF_MODULE_MODE AND GNSSSDR_PROTOBUF_PC_FOUND) if(TARGET PkgConfig::GNSSSDR_PROTOBUF_PC) target_link_libraries(protobuf::libprotobuf INTERFACE PkgConfig::GNSSSDR_PROTOBUF_PC) @@ -2396,6 +2475,33 @@ if(GNSSSDR_PROTOBUF_PC_FOUND) set_property(TARGET protobuf::libprotobuf APPEND PROPERTY INTERFACE_LINK_LIBRARIES "${GNSSSDR_PROTOBUF_PC_LDFLAGS}") endif() + + if(CMAKE_VERSION VERSION_LESS 3.1 OR + ((GNSSSDR_PROTOBUF_REQUIRED_CXX_STANDARD VERSION_EQUAL 17) AND + CMAKE_VERSION VERSION_LESS 3.8) OR + ((GNSSSDR_PROTOBUF_REQUIRED_CXX_STANDARD VERSION_EQUAL 20) AND + CMAKE_VERSION VERSION_LESS 3.12)) + if(MSVC) + if(NOT GNSSSDR_PROTOBUF_REQUIRED_CXX_STANDARD VERSION_LESS "17") + set(_GNSSSDR_PROTOBUF_CXX_STANDARD_OPTION + "/std:c++${GNSSSDR_PROTOBUF_REQUIRED_CXX_STANDARD}") + endif() + else() + set(_GNSSSDR_PROTOBUF_CXX_STANDARD_OPTION + "-std=c++${GNSSSDR_PROTOBUF_REQUIRED_CXX_STANDARD}") + endif() + if(_GNSSSDR_PROTOBUF_CXX_STANDARD_OPTION) + add_compile_options( + "$<$,CXX>:${_GNSSSDR_PROTOBUF_CXX_STANDARD_OPTION}>") + endif() + unset(_GNSSSDR_PROTOBUF_CXX_STANDARD_OPTION) + else() + set_property(TARGET protobuf::libprotobuf APPEND PROPERTY + INTERFACE_COMPILE_FEATURES + "cxx_std_${GNSSSDR_PROTOBUF_REQUIRED_CXX_STANDARD}") + endif() + message(STATUS + "Protobuf module fallback requires C++${GNSSSDR_PROTOBUF_REQUIRED_CXX_STANDARD}") endif() if(Protobuf_FOUND)