Skip to content

Commit

Permalink
Add cmake functions
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Carroll <mjcarroll@intrinsic.ai>
  • Loading branch information
mjcarroll committed May 11, 2023
1 parent 632524e commit dc60800
Show file tree
Hide file tree
Showing 3 changed files with 359 additions and 0 deletions.
77 changes: 77 additions & 0 deletions cmake/gz_msgs_factory.cmake
Original file line number Diff line number Diff line change
@@ -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()
107 changes: 107 additions & 0 deletions cmake/gz_msgs_protoc.cmake
Original file line number Diff line number Diff line change
@@ -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 "$<TARGET_FILE:${gz_msgs_protoc_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()
175 changes: 175 additions & 0 deletions gz-msgs-extras.cmake.in
Original file line number Diff line number Diff line change
@@ -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:$<TARGET_FILE:${message_lib}>)
else()
target_link_libraries(${target_link_messages_TARGET} PRIVATE
$<$<CXX_COMPILER_ID:GNU>:-Wl,--whole-archive>
$<$<CXX_COMPILER_ID:Clang>:-force_load>
$<$<CXX_COMPILER_ID:AppleClang>:-force_load> ${message_lib}
$<$<CXX_COMPILER_ID:GNU>:-Wl,--no-whole-archive>)
endif()
endforeach()


endfunction()

0 comments on commit dc60800

Please sign in to comment.