From 6645453e233f230ad8686d339409c065ff171795 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Thu, 11 May 2023 20:37:10 +0000 Subject: [PATCH] Add cmake functions Signed-off-by: Michael Carroll --- cmake/gz_msgs_factory.cmake | 77 ++++++++++++++++ cmake/gz_msgs_protoc.cmake | 107 ++++++++++++++++++++++ gz-msgs-extras.cmake.in | 175 ++++++++++++++++++++++++++++++++++++ 3 files changed, 359 insertions(+) create mode 100644 cmake/gz_msgs_factory.cmake create mode 100644 cmake/gz_msgs_protoc.cmake create mode 100644 gz-msgs-extras.cmake.in diff --git a/cmake/gz_msgs_factory.cmake b/cmake/gz_msgs_factory.cmake new file mode 100644 index 00000000..0b3a3e9a --- /dev/null +++ b/cmake/gz_msgs_factory.cmake @@ -0,0 +1,77 @@ +################################################## +# A function that calls protoc on a protobuf file +# Options: +# One value arguments: +# FACTORY_GEN_SCRIPT - Location of the factory generator script +# PROTO_PACKAGE - Protobuf package the file belongs to (e.g. ".gz.msgs") +# PROTOC_EXEC - Path to protoc +# INPUT_PROTO - Path to the input .proto file +# OUTPUT_CPP_DIR - Path where C++ files are saved +# OUTPUT_INCLUDES - A CMake variable name containing a list that the C++ header path should be appended to +# OUTPUT_CPP_HH_VAR - A CMake variable name containing a list that the C++ header path should be appended to +# OUTPUT_CPP_CC_VAR - A Cmake variable name containing a list that the C++ source path should be appended to +# Multi value arguments +# INPUT_PROTOS - Passed to protoc --proto_path +# PROTO_PATH - Passed to protoc --proto_path +function(gz_msgs_factory) + set(options "") + set(oneValueArgs + FACTORY_GEN_SCRIPT + PROTO_PACKAGE + OUTPUT_CPP_DIR + OUTPUT_CPP_HH_VAR + OUTPUT_CPP_CC_VAR) + set(multiValueArgs INPUT_PROTOS PROTO_PATH) + + cmake_parse_arguments(gz_msgs_factory "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + set(proto_package_dir ".") + if(gz_msgs_factory_PROTO_PACKAGE) + string(REPLACE "." "/" proto_package_dir ${gz_msgs_factory_PROTO_PACKAGE}) + endif() + + set(output_header "${gz_msgs_factory_OUTPUT_CPP_DIR}/${proto_package_dir}/MessageTypes.hh") + set(output_source "${gz_msgs_factory_OUTPUT_CPP_DIR}/${proto_package_dir}/register.cc") + + list(APPEND ${gz_msgs_factory_OUTPUT_CPP_HH_VAR} ${output_header}) + list(APPEND ${gz_msgs_factory_OUTPUT_CPP_CC_VAR} ${output_source}) + + list(APPEND output_files ${output_header}) + list(APPEND output_files ${output_source}) + + set(${gz_msgs_factory_OUTPUT_CPP_HH_VAR} ${${gz_msgs_factory_OUTPUT_CPP_HH_VAR}} PARENT_SCOPE) + set(${gz_msgs_factory_OUTPUT_CPP_CC_VAR} ${${gz_msgs_factory_OUTPUT_CPP_CC_VAR}} PARENT_SCOPE) + + set(depends_index) + + # Full path to an index file, which contains all defined message types for that proto file + foreach(proto_file ${generate_messages_MSGS_PROTOS}) + get_filename_component(FIL_WE ${proto_file} NAME_WE) + string(REPLACE "." "_" PACKAGE_UNDER ${gz_msgs_factory_PROTO_PACKAGE}) + string(REPLACE "." "_" MESSAGE_UNDER ${FIL_WE}) + set(input_index "${gz_msgs_factory_OUTPUT_CPP_DIR}/${PACKAGE_UNDER}_${MESSAGE_UNDER}.pb_index") + list(APPEND depends_index ${input_index}) + endforeach() + + + set(GENERATE_ARGS + --output-cpp-path "${gz_msgs_factory_OUTPUT_CPP_DIR}" + --proto-package "${gz_msgs_factory_PROTO_PACKAGE}" + --proto-path "${gz_msgs_factory_PROTO_PATH}" + --protos "${gz_msgs_factory_INPUT_PROTOS}" + ) + + add_custom_command( + OUTPUT ${output_files} + COMMAND Python3::Interpreter + ARGS ${gz_msgs_factory_FACTORY_GEN_SCRIPT} ${GENERATE_ARGS} + DEPENDS + ${depends_index} + # While the script is executed in the source directory, it does not write + # to the source tree. All outputs are stored in the build directory. + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMENT "Running factory generator" + VERBATIM + ) + +endfunction() diff --git a/cmake/gz_msgs_protoc.cmake b/cmake/gz_msgs_protoc.cmake new file mode 100644 index 00000000..32924fbb --- /dev/null +++ b/cmake/gz_msgs_protoc.cmake @@ -0,0 +1,107 @@ +################################################## +# A function that calls protoc on a protobuf file +# Options: +# GENERATE_CPP - generates c++ code for the message if specified +# One value arguments: +# PROTO_PACKAGE - Protobuf package the file belongs to (e.g. ".gz.msgs") +# PROTOC_EXEC - Path to protoc +# INPUT_PROTO - Path to the input .proto file +# OUTPUT_CPP_DIR - Path where C++ files are saved +# OUTPUT_INCLUDES - A CMake variable name containing a list that the C++ header path should be appended to +# OUTPUT_CPP_HH_VAR - A CMake variable name containing a list that the C++ header path should be appended to +# OUTPUT_CPP_CC_VAR - A Cmake variable name containing a list that the C++ source path should be appended to +# Multi value arguments +# PROTO_PATH - Passed to protoc --proto_path +function(gz_msgs_protoc) + set(options GENERATE_CPP) + set(oneValueArgs + MSGS_GEN_SCRIPT + PROTO_PACKAGE + PROTOC_EXEC + GZ_PROTOC_PLUGIN + INPUT_PROTO + OUTPUT_CPP_DIR + OUTPUT_INCLUDES + OUTPUT_CPP_HH_VAR + OUTPUT_DETAIL_CPP_HH_VAR + OUTPUT_CPP_CC_VAR) + set(multiValueArgs PROTO_PATH DEPENDENCY_PROTO_PATHS) + + cmake_parse_arguments(gz_msgs_protoc "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + get_filename_component(ABS_FIL ${gz_msgs_protoc_INPUT_PROTO} ABSOLUTE) + get_filename_component(FIL_WE ${gz_msgs_protoc_INPUT_PROTO} NAME_WE) + + set(protoc_args) + set(output_files) + + set(proto_package_dir ".") + if(gz_msgs_protoc_PROTO_PACKAGE) + string(REPLACE "." "/" proto_package_dir ${gz_msgs_protoc_PROTO_PACKAGE}) + endif() + + if(gz_msgs_protoc_GENERATE_CPP) + # Full path to generated header (${PROJECT_BINARY_DIR}/include/gz/msgs/foo.pb.h) + set(output_header "${gz_msgs_protoc_OUTPUT_CPP_DIR}/${proto_package_dir}/${FIL_WE}.pb.h") + # Full path to generated detail header (${PROJECT_BINARY_DIR}/include/gz/msgs/details/foo.pb.h) + set(output_detail_header "${gz_msgs_protoc_OUTPUT_CPP_DIR}/${proto_package_dir}/details/${FIL_WE}.pb.h") + # Full path to generated ignition header (${PROJECT_BINARY_DIR}/include/foo.pb.cc) + set(output_source "${gz_msgs_protoc_OUTPUT_CPP_DIR}/${proto_package_dir}/${FIL_WE}.pb.cc") + + # Full path to an index file, which contains all defined message types for that proto file + string(REPLACE "." "_" PACKAGE_UNDER ${gz_msgs_protoc_PROTO_PACKAGE}) + string(REPLACE "." "_" MESSAGE_UNDER ${FIL_WE}) + set(output_index "${gz_msgs_protoc_OUTPUT_CPP_DIR}/${PACKAGE_UNDER}_${MESSAGE_UNDER}.pb_index") + + # Generate a clean relative path (gz/msgs/foo.pb.h) + string(REPLACE "${PROJECT_BINARY_DIR}/include/" "" output_include ${output_header}) + list(APPEND ${gz_msgs_protoc_OUTPUT_INCLUDES} "${output_include}") + + list(APPEND ${gz_msgs_protoc_OUTPUT_CPP_HH_VAR} ${output_header}) + list(APPEND ${gz_msgs_protoc_OUTPUT_CPP_CC_VAR} ${output_source}) + list(APPEND ${gz_msgs_protoc_OUTPUT_DETAIL_CPP_HH_VAR} ${output_detail_header}) + + list(APPEND output_files ${output_header}) + list(APPEND output_files ${output_detail_header}) + list(APPEND output_files ${output_source}) + list(APPEND output_files ${output_index}) + + set(${gz_msgs_protoc_OUTPUT_INCLUDES} ${${gz_msgs_protoc_OUTPUT_INCLUDES}} PARENT_SCOPE) + set(${gz_msgs_protoc_OUTPUT_DETAIL_CPP_HH_VAR} ${${gz_msgs_protoc_OUTPUT_DETAIL_CPP_HH_VAR}} PARENT_SCOPE) + set(${gz_msgs_protoc_OUTPUT_CPP_HH_VAR} ${${gz_msgs_protoc_OUTPUT_CPP_HH_VAR}} PARENT_SCOPE) + set(${gz_msgs_protoc_OUTPUT_CPP_CC_VAR} ${${gz_msgs_protoc_OUTPUT_CPP_CC_VAR}} PARENT_SCOPE) + endif() + + set(GENERATE_ARGS + --protoc-exec "$" + --gz-generator-bin "${gz_msgs_protoc_GZ_PROTOC_PLUGIN}" + --proto-path "${gz_msgs_protoc_PROTO_PATH}" + --input-path "${ABS_FIL}" + ) + + if(gz_msgs_protoc_DEPENDENCY_PROTO_PATHS) + list(APPEND GENERATE_ARGS + --dependency-proto-paths "${gz_msgs_protoc_DEPENDENCY_PROTO_PATHS}" + ) + endif() + + if(${gz_msgs_protoc_GENERATE_CPP}) + list(APPEND GENERATE_ARGS + --generate-cpp + --output-cpp-path "${gz_msgs_protoc_OUTPUT_CPP_DIR}") + endif() + + add_custom_command( + OUTPUT ${output_files} + COMMAND Python3::Interpreter + ARGS ${gz_msgs_protoc_MSGS_GEN_SCRIPT} ${GENERATE_ARGS} + DEPENDS + ${ABS_FIL} + # While the script is executed in the source directory, it does not write + # to the source tree. All outputs are stored in the build directory. + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMENT "Running protoc on ${gz_msgs_protoc_INPUT_PROTO}" + VERBATIM + ) + +endfunction() diff --git a/gz-msgs-extras.cmake.in b/gz-msgs-extras.cmake.in new file mode 100644 index 00000000..ffef37eb --- /dev/null +++ b/gz-msgs-extras.cmake.in @@ -0,0 +1,175 @@ +# Copyright 2023 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# copied from gz-msgs/gz-msgs-extras.cmake + +find_package(Python3 REQUIRED COMPONENTS Interpreter) + +include(${@PROJECT_NAME@_DIR}/gz_msgs_protoc.cmake) +include(${@PROJECT_NAME@_DIR}/gz_msgs_factory.cmake) + +set(@PROJECT_NAME@_INSTALL_PATH "${@PROJECT_NAME@_DIR}/../../..") +cmake_path(NORMAL_PATH @PROJECT_NAME@_INSTALL_PATH OUTPUT_VARIABLE @PROJECT_NAME@_INSTALL_PATH) +set(PROTOC_NAME "@PROJECT_NAME@_protoc_plugin") +set(PROTO_SCRIPT_NAME "@PROJECT_NAME@_generate.py") +set(FACTORY_SCRIPT_NAME "@PROJECT_NAME@_generate_factory.py") + +set(@PROJECT_NAME@_PROTO_PATH ${@PROJECT_NAME@_INSTALL_PATH}/share/protos) +set(@PROJECT_NAME@_PROTO_GENERATOR_PLUGIN ${@PROJECT_NAME@_INSTALL_PATH}/bin/${PROTOC_NAME}) +set(@PROJECT_NAME@_PROTO_GENERATOR_SCRIPT ${@PROJECT_NAME@_INSTALL_PATH}/bin/${PROTO_SCRIPT_NAME}) +set(@PROJECT_NAME@_FACTORY_GENERATOR_SCRIPT ${@PROJECT_NAME@_INSTALL_PATH}/bin/${FACTORY_SCRIPT_NAME}) + +function(get_installed_messages) + set(options "") + set(oneValueArgs MESSAGES_PATH_VARIABLE MESSAGES_PROTOS_VARIABLE) + set(multiValueArgs DEPENDS) + + cmake_parse_arguments(get_installed_messages "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + set(@PROJECT_NAME@_INSTALL_PATH "${@PROJECT_NAME@_DIR}/../../..") + cmake_path(NORMAL_PATH @PROJECT_NAME@_INSTALL_PATH OUTPUT_VARIABLE @PROJECT_NAME@_INSTALL_PATH) + + set(@PROJECT_NAME@_PROTO_PATH ${@PROJECT_NAME@_INSTALL_PATH}/share/protos) + file (GLOB protos ${@PROJECT_NAME@_PROTO_PATH}/gz/msgs/*.proto) + set(${get_installed_messages_MESSAGES_PROTOS_VARIABLE} ${protos} PARENT_SCOPE) + set(${get_installed_messages_MESSAGES_PATH_VARIABLE} ${@PROJECT_NAME@_PROTO_PATH} PARENT_SCOPE) +endfunction() + +function(generate_messages) + set(options "") + set(oneValueArgs MSGS_PATH TARGET PROTO_PACKAGE) + set(multiValueArgs MSGS_PROTOS DEPENDENCIES) + + cmake_parse_arguments(generate_messages "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + string(REPLACE "." "_" gen_dir ${generate_messages_PROTO_PACKAGE}) + + set(depends_proto_paths) + set(depends_includes) + foreach(dep ${generate_messages_DEPENDENCIES}) + get_target_property(dep_proto_path ${dep} PROTO_DIR) + get_target_property(dep_proto_include_path ${dep} PROTO_INCLUDE_DIR) + list(APPEND depends_proto_paths ${dep_proto_path}) + list(APPEND depends_includes ${dep_proto_include_path}) + endforeach() + + foreach(proto_file ${generate_messages_MSGS_PROTOS}) + gz_msgs_protoc( + MSGS_GEN_SCRIPT + ${@PROJECT_NAME@_PROTO_GENERATOR_SCRIPT} + PROTO_PACKAGE + ${generate_messages_PROTO_PACKAGE} + GENERATE_CPP + INPUT_PROTO + ${proto_file} + PROTOC_EXEC + protobuf::protoc + GZ_PROTOC_PLUGIN + ${@PROJECT_NAME@_PROTO_GENERATOR_PLUGIN} + OUTPUT_CPP_DIR + "${PROJECT_BINARY_DIR}/${gen_dir}_gen" + OUTPUT_INCLUDES + gen_includes + OUTPUT_CPP_HH_VAR + gen_headers + OUTPUT_DETAIL_CPP_HH_VAR + gen_detail_headers + OUTPUT_CPP_CC_VAR + gen_sources + PROTO_PATH + ${generate_messages_MSGS_PATH} + DEPENDENCY_PROTO_PATHS + ${depends_proto_paths} + ) + endforeach() + + gz_msgs_factory( + FACTORY_GEN_SCRIPT + ${@PROJECT_NAME@_FACTORY_GENERATOR_SCRIPT} + PROTO_PACKAGE + ${generate_messages_PROTO_PACKAGE} + INPUT_PROTOS + ${generate_messages_MSGS_PROTOS} + OUTPUT_CPP_DIR + "${PROJECT_BINARY_DIR}/${gen_dir}_gen" + OUTPUT_CPP_HH_VAR + gen_factory_headers + OUTPUT_CPP_CC_VAR + gen_factory_sources + PROTO_PATH + ${generate_messages_MSGS_PATH} + ) + + set_source_files_properties( + ${gen_headers} + ${gen_detail_headers} + ${gen_sources} + ${gen_factory_headers} + ${gen_factory_sources} + PROPERTIES GENERATED TRUE) + + if(WIN32) + set_source_files_properties(${gen_sources} + COMPILE_FLAGS "/wd4100 /wd4512 /wd4127 /wd4068 /wd4244 /wd4267 /wd4251 /wd4146") + endif() + + add_library(${generate_messages_TARGET} ${gen_sources} ${gen_factory_sources}) + + # Export the messages path and dependency messages paths for potential dependent message libs + set(PROTO_DIR) + list(APPEND PROTO_DIR ${generate_messages_MSGS_PATH}) + list(APPEND PROTO_DIR ${depends_proto_paths}) + + set(PROTO_INCLUDE_DIR) + list(APPEND PROTO_INCLUDE_DIR ${PROJECT_BINARY_DIR}/${gen_dir}_gen) + list(APPEND PROTO_INCLUDE_DIR ${depends_includes}) + + set_target_properties(${generate_messages_TARGET} PROPERTIES PROTO_DIR "${PROTO_DIR}") + set_target_properties(${generate_messages_TARGET} PROPERTIES PROTO_INCLUDE_DIR "${PROTO_INCLUDE_DIR}") + + target_link_libraries(${generate_messages_TARGET} PUBLIC protobuf::libprotobuf gz-msgs10::gz-msgs10) + target_include_directories(${generate_messages_TARGET} PUBLIC ${PROJECT_BINARY_DIR}/${gen_dir}_gen ${depends_includes}) +endfunction() + +function(target_link_messages) + set(options PUBLIC PRIVATE) + set(oneValueArgs TARGET) + set(multiValueArgs MSG_TARGETS) + + cmake_parse_arguments(target_link_messages "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (target_link_messages_PUBLIC) + set(VISIBILITY PUBLIC) + elseif (target_link_messages_PRIVATE) + set(VISIBILITY PRIVATE) + endif() + + + foreach(message_lib ${target_link_messages_MSG_TARGETS}) + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + # MSVC link flag doesn't work with generator expressions + # TODO(mjcarroll) When CMake 3.24 is genrally available, use + # linking generator expressions as described here: + # https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#genex:LINK_LIBRARY + target_link_libraries(${target_link_messages_TARGET} ${VISIBILITY} -WHOLEARCHIVE:$) + else() + target_link_libraries(${target_link_messages_TARGET} PRIVATE + $<$:-Wl,--whole-archive> + $<$:-force_load> + $<$:-force_load> ${message_lib} + $<$:-Wl,--no-whole-archive>) + endif() + endforeach() + + +endfunction()