cmake_minimum_required(VERSION 3.18)
project(
    addressbook
    VERSION 0.1.0
    DESCRIPTION "Simple application using GBP messages with RTI Connext DDS."
    LANGUAGES CXX
)

# Load RTI Connext DDS
list(APPEND CMAKE_MODULE_PATH $ENV{CONNEXTDDS_DIR}/resource/cmake)
find_package(RTIConnextDDS REQUIRED)

# Load Protobuf
# When Protobuf is built from source, it is shipped with a CMake configuration file,
# while binary distributions will typically rely on the Find script provided by CMake.
# Protobuf_USE_CONFIG is a custom variable that can be set to control how to load
# Protobuf based on the build environment. You can simplify this logic if preferred.
if(Protobuf_USE_CONFIG)
    find_package(Protobuf CONFIG REQUIRED)
else()
    find_package(Protobuf REQUIRED)
endif()

# Connext directory containing additional .proto files used to enable
# DDS-specific options (i.e. omg/dds/descriptor.proto).
set(CONNEXTDDS_PROTO_DIR $ENV{CONNEXTDDS_DIR}/resource/proto)

# Source directory containing the .proto files
set(PROTO_DIR ${CMAKE_CURRENT_SOURCE_DIR}/proto)

# Directory where generated files will be placed by protoc and rtiddsgen
set(OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/proto)

###############################################################################
# Generate IDL & C++ source
###############################################################################
# File omg/dds/descriptor.proto must be imported by any .proto file that
# uses DDS-specific options, using the statement 'import "omg/dds/descriptor.proto"
# which will not be translated into IDL.
# The file must be processed with protoc to generate the C++ source files defining
# the DDS-specific options, but it cannot be converted to IDL, and thus it does not
# need to be processed by rtiddsgen.
# The command invokes protoc only with the built-in C++ plugin (cpp).
add_custom_command(
    OUTPUT
        ${OUT_DIR}/omg/dds/descriptor.pb.cc
        ${OUT_DIR}/omg/dds/descriptor.pb.h
    COMMAND ${CMAKE_COMMAND} -E make_directory ${OUT_DIR}
    COMMAND
        protoc
            --cpp_out=${OUT_DIR}
            -I ${CONNEXTDDS_PROTO_DIR}
            ${CONNEXTDDS_PROTO_DIR}/omg/dds/descriptor.proto
    DEPENDS
      ${CONNEXTDDS_PROTO_DIR}/omg/dds/descriptor.proto
)

# All .proto files must be processed by protoc to generate the Connext-enabled
# C++ source files, and the equivalent IDL files.
# This includes "built-in" files such as google/protobuf/timestamp.proto, which
# do not typically require processing with protoc, since they are already linked
# into the protobuf libraries.
# The .proto files for built-in types can be found in the Protocol Buffers
# source distribution, if not already included in the protoc distribution for a
# specific Operating System.
# The command invokes protoc with 3 plugins:
# - the built-in C++ plugin (cpp) to generate the C++ source files,
# - the Connext-specific C++ plugin (connext-cpp) to decorate the C++ source files
#   in order to use the data types with Connext.
# - the IDL4 plugin (idl4) to generate the equivalent IDL files.
# It is recommended to place all input files under a common directory tree, and
# to reference a common base directory in the inclusion path. This will allow
# protoc to find the relative paths of the input files, and to generate the
# output files under the same directory tree, starting from a common base output
# directory.
add_custom_command(
    OUTPUT
        ${OUT_DIR}/addressbook.pb.cc
        ${OUT_DIR}/addressbook.pb.h
        ${OUT_DIR}/addressbook.idl
        ${OUT_DIR}/google/protobuf/timestamp.pb.cc
        ${OUT_DIR}/google/protobuf/timestamp.pb.h
        ${OUT_DIR}/google/protobuf/timestamp.idl
    COMMAND ${CMAKE_COMMAND} -E make_directory ${OUT_DIR}
    COMMAND
        protoc
            --cpp_out=${OUT_DIR}
            --connext-cpp_out=${OUT_DIR}
            --idl4_out=${OUT_DIR}
            -I ${CONNEXTDDS_PROTO_DIR}
            -I ${PROTO_DIR}
            ${PROTO_DIR}/addressbook.proto
            ${PROTO_DIR}/google/protobuf/timestamp.proto
    DEPENDS
        ${PROTO_DIR}/addressbook.proto
        ${PROTO_DIR}/google/protobuf/timestamp.proto
)

# The IDL files generated by protoc must be processed by rtiddsgen to generate
# the C++ source files that fully integrate the Protocol Buffers C++ types with Connext.
# All rtiddsgen invocations should occur after all .proto files have been processed
# by protoc and converted to .idl, in order to guarantee that all generated #include's
# are valid. This is achieved by adding the .idl files as dependencies of the target.
# In this example, we are passing all .idl files to a single rtiddsgen invocation.
# We must add the base output directory to the include search path. Differently
# from protoc, rtiddsgen will not calculate the relative paths of each input files,
# but rather it will place every output file next to the corresponding input.
add_custom_command(
    OUTPUT
        ${OUT_DIR}/addressbook.cxx
        ${OUT_DIR}/addressbook.hpp
        ${OUT_DIR}/addressbookPlugin.cxx
        ${OUT_DIR}/addressbookPlugin.hpp
    COMMAND
        rtiddsgen
            -language C++11
            -standard PROTOBUF_CPP
            -I ${OUT_DIR}
            -replace
            ${OUT_DIR}/addressbook.idl
    DEPENDS
        ${OUT_DIR}/addressbook.idl
        ${OUT_DIR}/google/protobuf/timestamp.idl
)

# TODO (CODEGENII-2297) Delete custom command once resolved
add_custom_command(
    OUTPUT
        ${OUT_DIR}/google/protobuf/timestamp.cxx
        ${OUT_DIR}/google/protobuf/timestamp.hpp
        ${OUT_DIR}/google/protobuf/timestampPlugin.cxx
        ${OUT_DIR}/google/protobuf/timestampPlugin.hpp
    COMMAND
        rtiddsgen
            -language C++11
            -standard PROTOBUF_CPP
            -I ${OUT_DIR}
            -replace
            ${OUT_DIR}/google/protobuf/timestamp.idl
    DEPENDS
        ${OUT_DIR}/addressbook.idl
        ${OUT_DIR}/google/protobuf/timestamp.idl
)

###############################################################################
# Build and install the "type support" code as a standalone library
###############################################################################
add_library(addressbook STATIC
    ${OUT_DIR}/addressbook.pb.h
    ${OUT_DIR}/addressbook.pb.cc
    ${OUT_DIR}/addressbook.hpp
    ${OUT_DIR}/addressbook.cxx
    ${OUT_DIR}/addressbookPlugin.hpp
    ${OUT_DIR}/addressbookPlugin.cxx
    ${OUT_DIR}/google/protobuf/timestamp.pb.h
    ${OUT_DIR}/google/protobuf/timestamp.pb.cc
    ${OUT_DIR}/google/protobuf/timestamp.hpp
    ${OUT_DIR}/google/protobuf/timestamp.cxx
    ${OUT_DIR}/google/protobuf/timestampPlugin.hpp
    ${OUT_DIR}/google/protobuf/timestampPlugin.cxx
    ${OUT_DIR}/omg/dds/descriptor.pb.cc
    ${OUT_DIR}/omg/dds/descriptor.pb.h
)
target_include_directories(addressbook
    PUBLIC
        $<BUILD_INTERFACE:${OUT_DIR}>
        $<INSTALL_INTERFACE:include/proto>
)
target_link_libraries(addressbook
    PUBLIC
      protobuf::libprotobuf
      RTIConnextDDS::cpp2_api
)

###############################################################################
# Build publisher app
###############################################################################
add_executable(addressbook_publisher
    src/addressbook_publisher.cxx
)
target_link_libraries(addressbook_publisher
    PRIVATE
      addressbook
)

###############################################################################
# Build subscriber app
###############################################################################
add_executable(addressbook_subscriber
    src/addressbook_subscriber.cxx
)
target_link_libraries(addressbook_subscriber
    PRIVATE
      addressbook
)

###############################################################################
# Build an exquivalent DDS-only example
###############################################################################
# We can process the .idl files with rtiddsgen to generate interfaces for any
# of the programming languages supported by Connext.
# For example, we can generate C++ source files that use the "modern C++" API
# for the data types instead of the Protocol Buffers C++ types.
# To avoid confusion, we will generate the DDS-only example in a separate directory.
set(DDS_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dds)

# For addressbook.idl we also generate the example publisher and subscriber.
add_custom_command(
    OUTPUT
        ${DDS_OUT_DIR}/addressbook.cxx
        ${DDS_OUT_DIR}/addressbook.hpp
        ${DDS_OUT_DIR}/addressbookPlugin.cxx
        ${DDS_OUT_DIR}/addressbookPlugin.hpp
        ${DDS_OUT_DIR}/addressbook_subscriber.cxx
        ${DDS_OUT_DIR}/addressbook_publisher.cxx
        ${DDS_OUT_DIR}/application.hpp
    COMMAND
        rtiddsgen
            -language C++11
            -example $ENV{CONNEXTDDS_ARCH}
            -I ${OUT_DIR}
            -d ${DDS_OUT_DIR}
            -replace
            ${OUT_DIR}/addressbook.idl
    DEPENDS
        ${OUT_DIR}/addressbook.idl
)

# Process timestamp.idl separately to specify the output subdirectory
add_custom_command(
    OUTPUT
        ${DDS_OUT_DIR}/google/protobuf/timestamp.cxx
        ${DDS_OUT_DIR}/google/protobuf/timestamp.hpp
        ${DDS_OUT_DIR}/google/protobuf/timestampPlugin.cxx
        ${DDS_OUT_DIR}/google/protobuf/timestampPlugin.hpp
    COMMAND
        ${CMAKE_COMMAND} -E make_directory ${DDS_OUT_DIR}/google/protobuf
    COMMAND
        rtiddsgen
            -language C++11
            -I ${OUT_DIR}
            -d ${DDS_OUT_DIR}/google/protobuf
            -replace
            ${OUT_DIR}/google/protobuf/timestamp.idl
    DEPENDS
        ${OUT_DIR}/google/protobuf/timestamp.idl
)

# Build the generated code into a library
add_library(addressbook_dds STATIC
    ${DDS_OUT_DIR}/addressbook.hpp
    ${DDS_OUT_DIR}/addressbook.cxx
    ${DDS_OUT_DIR}/addressbookPlugin.hpp
    ${DDS_OUT_DIR}/addressbookPlugin.cxx
    ${DDS_OUT_DIR}/google/protobuf/timestamp.hpp
    ${DDS_OUT_DIR}/google/protobuf/timestamp.cxx
    ${DDS_OUT_DIR}/google/protobuf/timestampPlugin.hpp
    ${DDS_OUT_DIR}/google/protobuf/timestampPlugin.cxx
)
target_include_directories(addressbook_dds
    PUBLIC
        $<BUILD_INTERFACE:${DDS_OUT_DIR}>
        $<INSTALL_INTERFACE:include/dds>
)
target_link_libraries(addressbook_dds
    PUBLIC
      RTIConnextDDS::cpp2_api
)

# Build the DDS publisher app
add_executable(addressbook_publisher_dds
    ${DDS_OUT_DIR}/addressbook_publisher.cxx
)
target_link_libraries(addressbook_publisher_dds
    PRIVATE
      addressbook_dds
)

# Build the DDS publisher app
add_executable(addressbook_subscriber_dds
    ${DDS_OUT_DIR}/addressbook_subscriber.cxx
)
target_link_libraries(addressbook_subscriber_dds
    PRIVATE
      addressbook_dds
)

# Copy the USER_QOS_PROFILES.xml file to the build directory so that it can be used
# by the example applications.
add_custom_command(
    TARGET addressbook_publisher_dds POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        ${DDS_OUT_DIR}/USER_QOS_PROFILES.xml
        $<TARGET_FILE_DIR:addressbook_publisher_dds>
)

add_custom_command(
    TARGET addressbook_subscriber_dds POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        ${DDS_OUT_DIR}/USER_QOS_PROFILES.xml
        $<TARGET_FILE_DIR:addressbook_subscriber_dds>
)

###############################################################################
# Define install targets
###############################################################################
# Protobuf Types Library
install(TARGETS addressbook
    ARCHIVE DESTINATION lib
    LIBRARY DESTINATION lib
    RUNTIME DESTINATION bin
)

install(FILES
        ${PROTO_DIR}/addressbook.proto
    DESTINATION share/proto
)

install(FILES
        ${OUT_DIR}/addressbook.idl
    DESTINATION share/idl
)

install(FILES
        ${OUT_DIR}/addressbook.pb.h
        ${OUT_DIR}/addressbook.hpp
        ${OUT_DIR}/addressbookPlugin.hpp
    DESTINATION include/proto
)

install(FILES
        ${PROTO_DIR}/google/protobuf/timestamp.proto
    DESTINATION share/proto/google/protobuf
)

install(FILES
        ${OUT_DIR}/google/protobuf/timestamp.idl
    DESTINATION share/idl/google/protobuf
)

install(FILES
        ${OUT_DIR}/google/protobuf/timestamp.pb.h
        ${OUT_DIR}/google/protobuf/timestamp.hpp
        ${OUT_DIR}/google/protobuf/timestampPlugin.hpp
    DESTINATION include/proto/google/protobuf/
)

# Protobuf Publisher app
install(TARGETS addressbook_publisher
    RUNTIME DESTINATION bin
)

# Protobuf Subscriber app
install(TARGETS addressbook_subscriber
    RUNTIME DESTINATION bin
)

# DDS Types Library
install(TARGETS addressbook_dds
    ARCHIVE DESTINATION lib
    LIBRARY DESTINATION lib
    RUNTIME DESTINATION bin
)

install(FILES
        ${DDS_OUT_DIR}/addressbook.hpp
        ${DDS_OUT_DIR}/addressbookPlugin.hpp
    DESTINATION include/dds
)

install(FILES
        ${DDS_OUT_DIR}/google/protobuf/timestamp.hpp
        ${DDS_OUT_DIR}/google/protobuf/timestampPlugin.hpp
    DESTINATION include/dds/google/protobuf/
)

# DDS Publisher app
install(TARGETS addressbook_publisher_dds
    RUNTIME DESTINATION bin
)

# DDS Subscriber app
install(TARGETS addressbook_subscriber_dds
    RUNTIME DESTINATION bin
)
