From b826b09fd24471b9a90454745b68182c40258caf Mon Sep 17 00:00:00 2001 From: Benedek Kupper Date: Fri, 7 Nov 2025 14:58:44 +0100 Subject: [PATCH] cmake: create integration for code generation These changes make the protobuf code generation and build steps all managed by cmake, no additional steps are necessary. --- .github/workflows/ci.yml | 2 - CMakeLists.txt | 88 ++++++++++++++++++++++++++----- build_test.sh | 14 ----- cmake/GenerateEmbeddedProto.cmake | 69 ++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 30 deletions(-) create mode 100644 cmake/GenerateEmbeddedProto.cmake diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ca34a0..d8e3fef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,8 +30,6 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install gcovr run: pip install gcovr==7.0 - - name: Setup venv - run: python setup.py - name: Build test run: ./build_test.sh - name: Code coverage diff --git a/CMakeLists.txt b/CMakeLists.txt index a9d9e15..c7fdd4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,24 +28,84 @@ # the Netherlands # -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.21) -project(test_EmbeddedProto) +project(EmbeddedProto + HOMEPAGE_URL "https://EmbeddedProto.com" +) -set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -fprofile-arcs -ftest-coverage") -set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1) +set(EMBEDDED_PROTO_GEN_PATH ${CMAKE_CURRENT_LIST_DIR} CACHE STRING "Root path for EmbeddedProto code generator") +if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/venv" AND EMBEDDED_PROTO_GEN_PATH STREQUAL CMAKE_CURRENT_LIST_DIR) + find_package(Python 3 REQUIRED) + execute_process( + COMMAND ${Python_EXECUTABLE} setup.py --ignore_version_diff + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) +endif() -add_subdirectory(external/googletest) +include(cmake/GenerateEmbeddedProto.cmake) -file(GLOB src_files - "src/*.cpp" - "test/*.cpp" - "build/EAMS/*.cpp" +add_library(${PROJECT_NAME} STATIC) +target_sources(${PROJECT_NAME} PRIVATE + src/Fields.cpp + src/MessageInterface.cpp + src/ReadBufferSection.cpp +) +target_include_directories(${PROJECT_NAME} PUBLIC + src ) -include_directories(test test/mock src build/EAMS) -include_directories(external/googletest/googletest external/googletest/googletest/include - external/googletest/googlemock external/googletest/googlemock/include) +if(PROJECT_IS_TOP_LEVEL) + set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -fprofile-arcs -ftest-coverage") + set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1) + + add_subdirectory(external/googletest) + + file(GLOB src_files + "test/*.cpp" + ) + add_executable(test_${PROJECT_NAME} ${src_files}) -add_executable(test_EmbeddedProto ${src_files}) -target_link_libraries(test_EmbeddedProto gtest gmock) \ No newline at end of file + target_include_directories(test_${PROJECT_NAME} + PRIVATE + test + test/mock + ${CMAKE_CURRENT_BINARY_DIR}/EAMS + ) + target_link_libraries(test_${PROJECT_NAME} + PRIVATE + ${PROJECT_NAME} + gtest + gmock + ) + generate_embedded_proto(PROTO_HEADERS_1 + OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/EAMS + INCLUDE_DIRS + test/proto + PROTO_FILES + test/proto/simple_types.proto + test/proto/nested_message.proto + test/proto/repeated_fields.proto + test/proto/oneof_fields.proto + # Deliberately do not manually generate file_to_include.proto and subfolder/file_to_include_from_subfolder.proto + # to test the automatic generation of files from including them in include_other_files.proto. + test/proto/include_other_files.proto + test/proto/empty_message.proto + test/proto/optional_fields.proto + ) + generate_embedded_proto(PROTO_HEADERS_2 + OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/EAMS + INCLUDE_DIRS + test/proto + generator + PROTO_FILES + test/proto/string_bytes.proto + test/proto/field_options.proto + ) + add_custom_target(generate_protos + DEPENDS + ${PROTO_HEADERS_1} + ${PROTO_HEADERS_2} + ) + add_dependencies(test_${PROJECT_NAME} generate_protos) +endif() diff --git a/build_test.sh b/build_test.sh index a3baf99..36574dc 100755 --- a/build_test.sh +++ b/build_test.sh @@ -32,20 +32,6 @@ # Fail on first non-zero return code set -exuo pipefail -# Generate sources using the EAMS plugin. -mkdir -p ./build/EAMS -protoc --plugin=protoc-gen-eams=protoc-gen-eams -I./test/proto --eams_out=./build/EAMS ./test/proto/simple_types.proto -protoc --plugin=protoc-gen-eams=protoc-gen-eams -I./test/proto --eams_out=./build/EAMS ./test/proto/nested_message.proto -protoc --plugin=protoc-gen-eams=protoc-gen-eams -I./test/proto --eams_out=./build/EAMS ./test/proto/repeated_fields.proto -protoc --plugin=protoc-gen-eams=protoc-gen-eams -I./test/proto --eams_out=./build/EAMS ./test/proto/oneof_fields.proto -protoc --plugin=protoc-gen-eams=protoc-gen-eams -I./test/proto --eams_out=./build/EAMS ./test/proto/include_other_files.proto -# Delibertly do not manually generate file_to_include.proto and subfolder/file_to_include_from_subfolder.proto -# to test the automatic generation of files from including them in include_other_files.proto. -protoc --plugin=protoc-gen-eams=protoc-gen-eams -I./test/proto -I./generator --eams_out=./build/EAMS ./test/proto/string_bytes.proto -protoc --plugin=protoc-gen-eams=protoc-gen-eams -I./test/proto --eams_out=./build/EAMS ./test/proto/empty_message.proto -protoc --plugin=protoc-gen-eams=protoc-gen-eams -I./test/proto --eams_out=./build/EAMS ./test/proto/optional_fields.proto -protoc --plugin=protoc-gen-eams=protoc-gen-eams -I./test/proto -I./generator --eams_out=./build/EAMS ./test/proto/field_options.proto - # For validation and testing generate the same message using python mkdir -p ./build/python mkdir -p ./build/python/subfolder diff --git a/cmake/GenerateEmbeddedProto.cmake b/cmake/GenerateEmbeddedProto.cmake new file mode 100644 index 0000000..db9ca92 --- /dev/null +++ b/cmake/GenerateEmbeddedProto.cmake @@ -0,0 +1,69 @@ +function(generate_embedded_proto OUTPUT_VARIABLE) + # Define the expected named parameters + set(options) # No options/flags + # Single value arguments + # OUTPUT_DIR: where to place generated files + set(oneValueArgs OUTPUT_DIR) + # Multi-value arguments: multiple include directories + list of proto files + set(multiValueArgs INCLUDE_DIRS PROTO_FILES) + + # Parse the named parameters + cmake_parse_arguments(PROTO "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Validate required parameters + if(NOT PROTO_INCLUDE_DIRS) + message(FATAL_ERROR "INCLUDE_DIRS is required (use INCLUDE_DIRS ...)") + endif() + if(NOT PROTO_OUTPUT_DIR) + message(FATAL_ERROR "OUTPUT_DIR is required") + endif() + if(NOT PROTO_PROTO_FILES) + message(FATAL_ERROR "PROTO_FILES is required") + endif() + if(PROTO_PROTO_FILES STREQUAL "") + message(WARNING "PROTO_FILES is empty") + endif() + # If caller did not provide an output variable name (first positional arg), use a default + if(NOT OUTPUT_VARIABLE) + set(OUTPUT_VARIABLE "GENERATED_PROTO_FILES") + endif() + + file(MAKE_DIRECTORY ${PROTO_OUTPUT_DIR}) + get_filename_component(ABS_GENERATED_SRC_DIR ${PROTO_OUTPUT_DIR} ABSOLUTE) + + # Build include arguments (-I ...) from INCLUDE_DIRS + set(INCLUDE_ARGS) + foreach(INC_DIR ${PROTO_INCLUDE_DIRS}) + get_filename_component(ABS_INC_DIR ${INC_DIR} ABSOLUTE) + list(APPEND INCLUDE_ARGS -I${ABS_INC_DIR}) + endforeach() + + set(GENERATED_FILES) + # Process each protobuf file + foreach(PROTO_FILE ${PROTO_PROTO_FILES}) + # Get absolute path + get_filename_component(ABS_PROTO_FILE ${PROTO_FILE} ABSOLUTE) + # Get the filename without the .proto extension + get_filename_component(PROTO_NAME ${PROTO_FILE} NAME_WE) + + # Add the custom command to generate code for each proto file + add_custom_command( + OUTPUT "${ABS_GENERATED_SRC_DIR}/${PROTO_NAME}.h" + WORKING_DIRECTORY ${EMBEDDED_PROTO_GEN_PATH} + COMMAND protoc + ARGS + --plugin=protoc-gen-eams=protoc-gen-eams + ${INCLUDE_ARGS} + --eams_out=${ABS_GENERATED_SRC_DIR} + ${ABS_PROTO_FILE} + DEPENDS ${ABS_PROTO_FILE} ${EMBEDDED_PROTO_GEN_PATH}/protoc-gen-eams + COMMENT "Generating EmbeddedProto code for ${PROTO_FILE}" + VERBATIM + ) + # Record the generated file path so the caller can consume it + list(APPEND GENERATED_FILES "${ABS_GENERATED_SRC_DIR}/${PROTO_NAME}.h") + endforeach() + + # Export the list of generated files to the caller (parent scope) + set(${OUTPUT_VARIABLE} ${GENERATED_FILES} PARENT_SCOPE) +endfunction()