From 20b2f5569958b6aa4d2a7efdf02a3c37d87ee2ee Mon Sep 17 00:00:00 2001 From: Rex Schilasky <49162693+rex-schilasky@users.noreply.github.com> Date: Fri, 26 Apr 2024 13:33:34 +0200 Subject: [PATCH 01/11] [app] ecalmon layer detection logic (#1568) ecalmon_gui / tui layer detection improved --- .../src/widgets/models/topic_tree_item.cpp | 33 ++++++++++--------- app/mon/mon_tui/src/model/monitor.hpp | 5 ++- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/app/mon/mon_gui/src/widgets/models/topic_tree_item.cpp b/app/mon/mon_gui/src/widgets/models/topic_tree_item.cpp index 6bf48e2cb3..eb2e68e00c 100644 --- a/app/mon/mon_gui/src/widgets/models/topic_tree_item.cpp +++ b/app/mon/mon_gui/src/widgets/models/topic_tree_item.cpp @@ -209,22 +209,25 @@ QVariant TopicTreeItem::data(Columns column, Qt::ItemDataRole role) const for (const auto& layer : layer_pb) { QString this_layer_string; - switch (layer.type()) + if (layer.confirmed()) { - case eCAL::pb::eTLayerType::tl_ecal_tcp: - this_layer_string = "tcp"; - break; - case eCAL::pb::eTLayerType::tl_ecal_udp_mc: - this_layer_string = "udp_mc"; - break; - case eCAL::pb::eTLayerType::tl_ecal_shm: - this_layer_string = "shm"; - break; - case eCAL::pb::eTLayerType::tl_all: - this_layer_string = "all"; - break; - default: - this_layer_string = ("Unknown (" + QString::number((int)layer.type()) + ")"); + switch (layer.type()) + { + case eCAL::pb::eTLayerType::tl_ecal_tcp: + this_layer_string = "tcp"; + break; + case eCAL::pb::eTLayerType::tl_ecal_udp_mc: + this_layer_string = "udp_mc"; + break; + case eCAL::pb::eTLayerType::tl_ecal_shm: + this_layer_string = "shm"; + break; + case eCAL::pb::eTLayerType::tl_all: + this_layer_string = "all"; + break; + default: + this_layer_string = ("Unknown (" + QString::number((int)layer.type()) + ")"); + } } if (!layer_string.isEmpty() && !this_layer_string.isEmpty()) diff --git a/app/mon/mon_tui/src/model/monitor.hpp b/app/mon/mon_tui/src/model/monitor.hpp index 1a87994939..63170a3ab6 100644 --- a/app/mon/mon_tui/src/model/monitor.hpp +++ b/app/mon/mon_tui/src/model/monitor.hpp @@ -204,7 +204,10 @@ class MonitorModel topic.type_descriptor = std::move(*t.mutable_tdatatype()->mutable_desc()); for(auto &tl: t.tlayer()) { - topic.transport_layers.emplace_back(TopicTransportLayer(tl.type())); + if (tl.confirmed()) + { + topic.transport_layers.emplace_back(TopicTransportLayer(tl.type())); + } } topic.size = t.tsize(); topic.local_connections_count = t.connections_loc(); From 5e99ac634692ddf74eb3bd659dfd134c0377ca50 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Fri, 26 Apr 2024 13:44:47 +0200 Subject: [PATCH 02/11] [Core] Added missing include (#1572) --- ecal/core/include/ecal/ecal_util.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ecal/core/include/ecal/ecal_util.h b/ecal/core/include/ecal/ecal_util.h index 7cfc6d2b04..0a666ee344 100644 --- a/ecal/core/include/ecal/ecal_util.h +++ b/ecal/core/include/ecal/ecal_util.h @@ -27,6 +27,8 @@ #include #include #include + +#include #include #include #include From 28279a6349a80cc6148963e80da736ae33e415b3 Mon Sep 17 00:00:00 2001 From: Peguen <73380451+Peguen@users.noreply.github.com> Date: Fri, 26 Apr 2024 13:46:11 +0200 Subject: [PATCH 03/11] [CMake] Added CMAKE_POSITION_INDEPENDENT_CODE ON to qt relevant builds (#1569) --- app/mon/mon_plugins/monitor_tree_view/CMakeLists.txt | 2 ++ lib/CustomQt/CMakeLists.txt | 2 ++ lib/CustomTclap/CMakeLists.txt | 2 ++ lib/QEcalParser/CMakeLists.txt | 2 ++ lib/ThreadingUtils/CMakeLists.txt | 2 ++ lib/ecal_utils/CMakeLists.txt | 2 ++ thirdparty/qwt/CMakeLists.txt | 2 ++ 7 files changed, 14 insertions(+) diff --git a/app/mon/mon_plugins/monitor_tree_view/CMakeLists.txt b/app/mon/mon_plugins/monitor_tree_view/CMakeLists.txt index bd2f1c99a2..2e86602c2c 100644 --- a/app/mon/mon_plugins/monitor_tree_view/CMakeLists.txt +++ b/app/mon/mon_plugins/monitor_tree_view/CMakeLists.txt @@ -18,6 +18,8 @@ project(MonitorTreeView) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + # Legacy Qt5 (pre 5.15) support as suggested by teh Qt Documentation: # https://doc.qt.io/qt-6/cmake-qt5-and-qt6-compatibility.html#supporting-older-qt-5-versions find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Widgets) diff --git a/lib/CustomQt/CMakeLists.txt b/lib/CustomQt/CMakeLists.txt index 0e93dba090..24fa75d6f4 100644 --- a/lib/CustomQt/CMakeLists.txt +++ b/lib/CustomQt/CMakeLists.txt @@ -18,6 +18,8 @@ project(CustomQt) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + # Legacy Qt5 (pre 5.15) support as suggested by teh Qt Documentation: # https://doc.qt.io/qt-6/cmake-qt5-and-qt6-compatibility.html#supporting-older-qt-5-versions find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Widgets) diff --git a/lib/CustomTclap/CMakeLists.txt b/lib/CustomTclap/CMakeLists.txt index ad8b35f214..daaae09d15 100644 --- a/lib/CustomTclap/CMakeLists.txt +++ b/lib/CustomTclap/CMakeLists.txt @@ -18,6 +18,8 @@ project(CustomTclap) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + find_package(tclap REQUIRED) set(include_files diff --git a/lib/QEcalParser/CMakeLists.txt b/lib/QEcalParser/CMakeLists.txt index 9a5fda1be6..1ee2a39801 100644 --- a/lib/QEcalParser/CMakeLists.txt +++ b/lib/QEcalParser/CMakeLists.txt @@ -18,6 +18,8 @@ project(QEcalParser) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + # Legacy Qt5 (pre 5.15) support as suggested by teh Qt Documentation: # https://doc.qt.io/qt-6/cmake-qt5-and-qt6-compatibility.html#supporting-older-qt-5-versions find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Widgets) diff --git a/lib/ThreadingUtils/CMakeLists.txt b/lib/ThreadingUtils/CMakeLists.txt index 28d9268c07..a8222da9e1 100644 --- a/lib/ThreadingUtils/CMakeLists.txt +++ b/lib/ThreadingUtils/CMakeLists.txt @@ -18,6 +18,8 @@ project(ThreadingUtils) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + find_package(Threads REQUIRED) set (threading_utils_includes diff --git a/lib/ecal_utils/CMakeLists.txt b/lib/ecal_utils/CMakeLists.txt index c6a4c6cd49..2f31c93c3c 100644 --- a/lib/ecal_utils/CMakeLists.txt +++ b/lib/ecal_utils/CMakeLists.txt @@ -17,6 +17,8 @@ # ========================= eCAL LICENSE ================================= project(ecal-utils) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + find_package(Threads REQUIRED) set (ecal_utils_includes diff --git a/thirdparty/qwt/CMakeLists.txt b/thirdparty/qwt/CMakeLists.txt index 61173c8cf8..5db44b880f 100644 --- a/thirdparty/qwt/CMakeLists.txt +++ b/thirdparty/qwt/CMakeLists.txt @@ -7,6 +7,8 @@ endif() project(qwt VERSION 6.2.0) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + # Legacy Qt5 (pre 5.15) support as suggested by teh Qt Documentation: # https://doc.qt.io/qt-6/cmake-qt5-and-qt6-compatibility.html#supporting-older-qt-5-versions find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets OpenGL Svg Concurrent PrintSupport) From 946ef7f681ec9759002f7092ddd493156f624a25 Mon Sep 17 00:00:00 2001 From: Rex Schilasky <49162693+rex-schilasky@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:54:00 +0200 Subject: [PATCH 04/11] [core] all eCAL writer Create/Destroy functions replaced by contructor/destructor + unique_ptr (#1573) --- ecal/core/src/readwrite/ecal_writer.cpp | 127 ++++++++---------- ecal/core/src/readwrite/ecal_writer.h | 9 +- ecal/core/src/readwrite/ecal_writer_base.h | 6 - .../src/readwrite/shm/ecal_writer_shm.cpp | 53 ++------ ecal/core/src/readwrite/shm/ecal_writer_shm.h | 8 +- .../src/readwrite/tcp/ecal_writer_tcp.cpp | 48 ++----- ecal/core/src/readwrite/tcp/ecal_writer_tcp.h | 10 +- .../src/readwrite/udp/ecal_writer_udp_mc.cpp | 50 ++----- .../src/readwrite/udp/ecal_writer_udp_mc.h | 7 +- 9 files changed, 105 insertions(+), 213 deletions(-) diff --git a/ecal/core/src/readwrite/ecal_writer.cpp b/ecal/core/src/readwrite/ecal_writer.cpp index 806dedab4a..6b77f7aa23 100644 --- a/ecal/core/src/readwrite/ecal_writer.cpp +++ b/ecal/core/src/readwrite/ecal_writer.cpp @@ -91,7 +91,7 @@ namespace eCAL { // initialize layer modes with configuration settings #if ECAL_CORE_TRANSPORT_UDP - m_writer.udp_mc_mode.requested = Config::GetPublisherUdpMulticastMode(); + m_writer.udp_mode.requested = Config::GetPublisherUdpMulticastMode(); #endif #if ECAL_CORE_TRANSPORT_SHM m_writer.shm_mode.requested = Config::GetPublisherShmMode(); @@ -146,7 +146,7 @@ namespace eCAL Register(false); // create udp multicast layer - SetUseUdpMC(m_writer.udp_mc_mode.requested); + SetUseUdpMC(m_writer.udp_mode.requested); // create shm layer SetUseShm(m_writer.shm_mode.requested); @@ -176,17 +176,17 @@ namespace eCAL // destroy udp multicast writer #if ECAL_CORE_TRANSPORT_UDP - m_writer.udp_mc.Destroy(); + m_writer.udp.reset(); #endif // destroy memory file writer #if ECAL_CORE_TRANSPORT_SHM - m_writer.shm.Destroy(); + m_writer.shm.reset(); #endif // destroy tcp writer #if ECAL_CORE_TRANSPORT_TCP - m_writer.tcp.Destroy(); + m_writer.tcp.reset(); #endif // reset defaults @@ -322,6 +322,8 @@ namespace eCAL bool CDataWriter::ShmSetBufferCount(size_t buffering_) { #if ECAL_CORE_TRANSPORT_SHM + if (!m_writer.shm) return false; + if (buffering_ < 1) { Logging::Log(log_level_error, m_topic_name + "::CDataWriter::ShmSetBufferCount minimal number of memory files is 1 !"); @@ -332,7 +334,7 @@ namespace eCAL // adapt number of used memory files if (m_created) { - m_writer.shm.SetBufferCount(buffering_); + m_writer.shm->SetBufferCount(buffering_); } return true; @@ -423,7 +425,7 @@ namespace eCAL const bool allow_zero_copy = m_zero_copy // zero copy mode activated by user && m_writer.shm_mode.activated // shm layer active - && !m_writer.udp_mc_mode.activated + && !m_writer.udp_mode.activated && !m_writer.tcp_mode.activated; // create a payload copy for all layer @@ -443,7 +445,7 @@ namespace eCAL // SHM //////////////////////////////////////////////////////////////////////////// #if ECAL_CORE_TRANSPORT_SHM - if (m_writer.shm_mode.activated) + if (m_writer.shm && m_writer.shm_mode.activated) { #ifndef NDEBUG // log it @@ -465,7 +467,7 @@ namespace eCAL wattr.acknowledge_timeout_ms = m_acknowledge_timeout_ms; // prepare send - if (m_writer.shm.PrepareWrite(wattr)) + if (m_writer.shm->PrepareWrite(wattr)) { // register new to update listening subscribers and rematch Register(true); @@ -476,7 +478,7 @@ namespace eCAL if (allow_zero_copy) { // write to shm layer (write content into the opened memory file without additional copy) - shm_sent = m_writer.shm.Write(payload_, wattr); + shm_sent = m_writer.shm->Write(payload_, wattr); } // multiple layer are active -> we make a copy and use that one else @@ -484,7 +486,7 @@ namespace eCAL // wrap the buffer into a payload object CBufferPayloadWriter payload_buf(m_payload_buffer.data(), m_payload_buffer.size()); // write to shm layer (write content into the opened memory file without additional copy) - shm_sent = m_writer.shm.Write(payload_buf, wattr); + shm_sent = m_writer.shm->Write(payload_buf, wattr); } m_writer.shm_mode.confirmed = true; @@ -509,7 +511,7 @@ namespace eCAL // UDP (MC) //////////////////////////////////////////////////////////////////////////// #if ECAL_CORE_TRANSPORT_UDP - if (m_writer.udp_mc_mode.activated) + if (m_writer.udp && m_writer.udp_mode.activated) { #ifndef NDEBUG // log it @@ -537,7 +539,7 @@ namespace eCAL wattr.loopback = loopback; // prepare send - if (m_writer.udp_mc.PrepareWrite(wattr)) + if (m_writer.udp->PrepareWrite(wattr)) { // register new to update listening subscribers and rematch Register(true); @@ -545,8 +547,8 @@ namespace eCAL } // write to udp multicast layer - udp_mc_sent = m_writer.udp_mc.Write(m_payload_buffer.data(), wattr); - m_writer.udp_mc_mode.confirmed = true; + udp_mc_sent = m_writer.udp->Write(m_payload_buffer.data(), wattr); + m_writer.udp_mode.confirmed = true; } written |= udp_mc_sent; @@ -568,7 +570,7 @@ namespace eCAL // TCP //////////////////////////////////////////////////////////////////////////// #if ECAL_CORE_TRANSPORT_TCP - if (m_writer.tcp_mode.activated) + if (m_writer.tcp && m_writer.tcp_mode.activated) { #ifndef NDEBUG // log it @@ -587,7 +589,7 @@ namespace eCAL wattr.time = time_; // write to tcp layer - tcp_sent = m_writer.tcp.Write(m_payload_buffer.data(), wattr); + tcp_sent = m_writer.tcp->Write(m_payload_buffer.data(), wattr); m_writer.tcp_mode.confirmed = true; } written |= tcp_sent; @@ -625,13 +627,13 @@ namespace eCAL // add a new local subscription #if ECAL_CORE_TRANSPORT_UDP - m_writer.udp_mc.AddLocConnection(local_info_.process_id, local_info_.topic_id, reader_par_); + if (m_writer.udp) m_writer.udp->AddLocConnection(local_info_.process_id, local_info_.topic_id, reader_par_); #endif #if ECAL_CORE_TRANSPORT_SHM - m_writer.shm.AddLocConnection(local_info_.process_id, local_info_.topic_id, reader_par_); + if (m_writer.shm) m_writer.shm->AddLocConnection(local_info_.process_id, local_info_.topic_id, reader_par_); #endif #if ECAL_CORE_TRANSPORT_TCP - m_writer.tcp.AddLocConnection(local_info_.process_id, local_info_.topic_id, reader_par_); + if (m_writer.tcp) m_writer.tcp->AddLocConnection(local_info_.process_id, local_info_.topic_id, reader_par_); #endif #ifndef NDEBUG @@ -650,13 +652,13 @@ namespace eCAL // remove a local subscription #if ECAL_CORE_TRANSPORT_UDP - m_writer.udp_mc.RemLocConnection(local_info_.process_id, local_info_.topic_id); + if (m_writer.udp) m_writer.udp->RemLocConnection(local_info_.process_id, local_info_.topic_id); #endif #if ECAL_CORE_TRANSPORT_SHM - m_writer.shm.RemLocConnection(local_info_.process_id, local_info_.topic_id); + if (m_writer.shm) m_writer.shm->RemLocConnection(local_info_.process_id, local_info_.topic_id); #endif #if ECAL_CORE_TRANSPORT_TCP - m_writer.tcp.RemLocConnection(local_info_.process_id, local_info_.topic_id); + if (m_writer.tcp) m_writer.tcp->RemLocConnection(local_info_.process_id, local_info_.topic_id); #endif #ifndef NDEBUG @@ -679,13 +681,13 @@ namespace eCAL // add a new external subscription #if ECAL_CORE_TRANSPORT_UDP - m_writer.udp_mc.AddExtConnection(external_info_.host_name, external_info_.process_id, external_info_.topic_id, reader_par_); + if (m_writer.udp) m_writer.udp->AddExtConnection(external_info_.host_name, external_info_.process_id, external_info_.topic_id, reader_par_); #endif #if ECAL_CORE_TRANSPORT_SHM - m_writer.shm.AddExtConnection(external_info_.host_name, external_info_.process_id, external_info_.topic_id, reader_par_); + if (m_writer.shm) m_writer.shm->AddExtConnection(external_info_.host_name, external_info_.process_id, external_info_.topic_id, reader_par_); #endif #if ECAL_CORE_TRANSPORT_TCP - m_writer.tcp.AddExtConnection(external_info_.host_name, external_info_.process_id, external_info_.topic_id, reader_par_); + if (m_writer.tcp) m_writer.tcp->AddExtConnection(external_info_.host_name, external_info_.process_id, external_info_.topic_id, reader_par_); #endif #ifndef NDEBUG @@ -704,13 +706,13 @@ namespace eCAL // remove external subscription #if ECAL_CORE_TRANSPORT_UDP - m_writer.udp_mc.RemExtConnection(external_info_.host_name, external_info_.process_id, external_info_.topic_id); + if (m_writer.udp) m_writer.udp->RemExtConnection(external_info_.host_name, external_info_.process_id, external_info_.topic_id); #endif #if ECAL_CORE_TRANSPORT_SHM - m_writer.shm.RemExtConnection(external_info_.host_name, external_info_.process_id, external_info_.topic_id); + if (m_writer.shm) m_writer.shm->RemExtConnection(external_info_.host_name, external_info_.process_id, external_info_.topic_id); #endif #if ECAL_CORE_TRANSPORT_TCP - m_writer.tcp.RemExtConnection(external_info_.host_name, external_info_.process_id, external_info_.topic_id); + if (m_writer.tcp) m_writer.tcp->RemExtConnection(external_info_.host_name, external_info_.process_id, external_info_.topic_id); #endif } @@ -815,36 +817,39 @@ namespace eCAL #if ECAL_CORE_TRANSPORT_UDP // udp multicast layer + if (m_writer.udp) { eCAL::Registration::TLayer udp_tlayer; udp_tlayer.type = tl_ecal_udp_mc; udp_tlayer.version = 1; - udp_tlayer.confirmed = m_writer.udp_mc_mode.confirmed; - udp_tlayer.par_layer.layer_par_udpmc = m_writer.udp_mc.GetConnectionParameter().layer_par_udpmc; + udp_tlayer.confirmed = m_writer.udp_mode.confirmed; + udp_tlayer.par_layer.layer_par_udpmc = m_writer.udp->GetConnectionParameter().layer_par_udpmc; ecal_reg_sample_topic.tlayer.push_back(udp_tlayer); } #endif #if ECAL_CORE_TRANSPORT_SHM // shm layer + if (m_writer.shm) { eCAL::Registration::TLayer shm_tlayer; shm_tlayer.type = tl_ecal_shm; shm_tlayer.version = 1; shm_tlayer.confirmed = m_writer.shm_mode.confirmed; - shm_tlayer.par_layer.layer_par_shm = m_writer.shm.GetConnectionParameter().layer_par_shm; + shm_tlayer.par_layer.layer_par_shm = m_writer.shm->GetConnectionParameter().layer_par_shm; ecal_reg_sample_topic.tlayer.push_back(shm_tlayer); } #endif #if ECAL_CORE_TRANSPORT_TCP // tcp layer + if (m_writer.tcp) { eCAL::Registration::TLayer tcp_tlayer; tcp_tlayer.type = tl_ecal_tcp; tcp_tlayer.version = 1; tcp_tlayer.confirmed = m_writer.tcp_mode.confirmed; - tcp_tlayer.par_layer.layer_par_tcp = m_writer.tcp.GetConnectionParameter().layer_par_tcp; + tcp_tlayer.par_layer.layer_par_tcp = m_writer.tcp->GetConnectionParameter().layer_par_tcp; ecal_reg_sample_topic.tlayer.push_back(tcp_tlayer); } #endif @@ -971,7 +976,7 @@ namespace eCAL void CDataWriter::SetUseUdpMC(TLayer::eSendMode mode_) { #if ECAL_CORE_TRANSPORT_UDP - m_writer.udp_mc_mode.requested = mode_; + m_writer.udp_mode.requested = mode_; if (!m_created) return; // log send mode @@ -981,20 +986,14 @@ namespace eCAL { case TLayer::eSendMode::smode_auto: case TLayer::eSendMode::smode_on: - if (m_writer.udp_mc.Create(m_host_name, m_topic_name, m_topic_id)) - { + m_writer.udp = std::make_unique(m_host_name, m_topic_name, m_topic_id); #ifndef NDEBUG - Logging::Log(log_level_debug4, m_topic_name + "::CDataWriter::Create::UDP_MC_WRITER - SUCCESS"); + Logging::Log(log_level_debug4, m_topic_name + "::CDataWriter::Create::UDP_MC_WRITER"); #endif - } - else - { - Logging::Log(log_level_error, m_topic_name + "::CDataWriter::Create::UDP_MC_WRITER - FAILED"); - } break; case TLayer::eSendMode::smode_none: case TLayer::eSendMode::smode_off: - m_writer.udp_mc.Destroy(); + m_writer.udp.reset(); break; } #endif // ECAL_CORE_TRANSPORT_UDP @@ -1013,20 +1012,14 @@ namespace eCAL { case TLayer::eSendMode::smode_auto: case TLayer::eSendMode::smode_on: - if (m_writer.shm.Create(m_host_name, m_topic_name, m_topic_id)) - { + m_writer.shm = std::make_unique(m_host_name, m_topic_name, m_topic_id); #ifndef NDEBUG - Logging::Log(log_level_debug4, m_topic_name + "::CDataWriter::Create::SHM_WRITER - SUCCESS"); + Logging::Log(log_level_debug4, m_topic_name + "::CDataWriter::Create::SHM_WRITER"); #endif - } - else - { - Logging::Log(log_level_error, m_topic_name + "::CDataWriter::Create::SHM_WRITER - FAILED"); - } break; case TLayer::eSendMode::smode_none: case TLayer::eSendMode::smode_off: - m_writer.shm.Destroy(); + m_writer.shm.reset(); break; } #endif // ECAL_CORE_TRANSPORT_SHM @@ -1045,20 +1038,14 @@ namespace eCAL { case TLayer::eSendMode::smode_auto: case TLayer::eSendMode::smode_on: - if (m_writer.tcp.Create(m_host_name, m_topic_name, m_topic_id)) - { + m_writer.tcp = std::make_unique(m_host_name, m_topic_name, m_topic_id); #ifndef NDEBUG - Logging::Log(log_level_debug4, m_topic_name + "::CDataWriter::Create::TCP_WRITER - SUCCESS"); + Logging::Log(log_level_debug4, m_topic_name + "::CDataWriter::Create::TCP_WRITER - SUCCESS"); #endif - } - else - { - Logging::Log(log_level_error, m_topic_name + "::CDataWriter::Create::TCP_WRITER - FAILED"); - } break; case TLayer::eSendMode::smode_none: case TLayer::eSendMode::smode_off: - m_writer.tcp.Destroy(); + m_writer.tcp.reset(); break; } #else // ECAL_CORE_TRANSPORT_TCP @@ -1069,13 +1056,13 @@ namespace eCAL bool CDataWriter::CheckWriterModes() { // if nothing is activated, we use defaults shm = auto, udp = auto - if ((m_writer.udp_mc_mode.requested == TLayer::smode_off) + if ((m_writer.udp_mode.requested == TLayer::smode_off) && (m_writer.shm_mode.requested == TLayer::smode_off) && (m_writer.tcp_mode.requested == TLayer::smode_off) ) { #if ECAL_CORE_TRANSPORT_UDP - m_writer.udp_mc_mode.requested = TLayer::smode_auto; + m_writer.udp_mode.requested = TLayer::smode_auto; #endif #if ECAL_CORE_TRANSPORT_SHM m_writer.shm_mode.requested = TLayer::smode_auto; @@ -1092,9 +1079,9 @@ namespace eCAL { bool new_local_layer(false); #if ECAL_CORE_TRANSPORT_UDP - if (m_writer.udp_mc_mode.requested != TLayer::smode_on) + if (m_writer.udp_mode.requested != TLayer::smode_on) { - m_writer.udp_mc_mode.requested = TLayer::smode_on; + m_writer.udp_mode.requested = TLayer::smode_on; new_local_layer = true; } #else @@ -1108,7 +1095,7 @@ namespace eCAL #endif if (new_local_layer) { - if (m_writer.udp_mc_mode.requested == TLayer::smode_on) + if (m_writer.udp_mode.requested == TLayer::smode_on) { Logging::Log(log_level_warning, m_topic_name + "::CDataWriter: Switched to udp for local communication."); SetUseUdpMC(TLayer::smode_on); @@ -1122,7 +1109,7 @@ namespace eCAL } if ( (m_writer.tcp_mode.requested == TLayer::smode_auto) - && (m_writer.udp_mc_mode.requested == TLayer::smode_auto) + && (m_writer.udp_mode.requested == TLayer::smode_auto) ) { Logging::Log(log_level_error, m_topic_name + "::CDataWriter::Send: TCP layer and UDP layer are both set to auto mode - Publication failed !"); @@ -1133,11 +1120,11 @@ namespace eCAL //////////////////////////////////////////////////////////////////////////// // UDP (MC) //////////////////////////////////////////////////////////////////////////// - if (((m_writer.udp_mc_mode.requested == TLayer::smode_auto) && m_ext_subscribed) - || (m_writer.udp_mc_mode.requested == TLayer::smode_on) + if (((m_writer.udp_mode.requested == TLayer::smode_auto) && m_ext_subscribed) + || (m_writer.udp_mode.requested == TLayer::smode_on) ) { - m_writer.udp_mc_mode.activated = true; + m_writer.udp_mode.activated = true; } #endif diff --git a/ecal/core/src/readwrite/ecal_writer.h b/ecal/core/src/readwrite/ecal_writer.h index 51b60dbc0b..f3f87b4789 100644 --- a/ecal/core/src/readwrite/ecal_writer.h +++ b/ecal/core/src/readwrite/ecal_writer.h @@ -48,6 +48,7 @@ #include "tcp/ecal_writer_tcp.h" #endif +#include #include #include #include @@ -198,18 +199,18 @@ namespace eCAL bool confirmed = false; }; - SWriterMode udp_mc_mode; + SWriterMode udp_mode; SWriterMode tcp_mode; SWriterMode shm_mode; #if ECAL_CORE_TRANSPORT_UDP - CDataWriterUdpMC udp_mc; + std::unique_ptr udp; #endif #if ECAL_CORE_TRANSPORT_SHM - CDataWriterSHM shm; + std::unique_ptr shm; #endif #if ECAL_CORE_TRANSPORT_TCP - CDataWriterTCP tcp; + std::unique_ptr tcp; #endif }; SWriter m_writer; diff --git a/ecal/core/src/readwrite/ecal_writer_base.h b/ecal/core/src/readwrite/ecal_writer_base.h index 40188b65b1..dfb8c00ceb 100644 --- a/ecal/core/src/readwrite/ecal_writer_base.h +++ b/ecal/core/src/readwrite/ecal_writer_base.h @@ -38,14 +38,10 @@ namespace eCAL class CDataWriterBase { public: - CDataWriterBase() : m_created(false) {}; virtual ~CDataWriterBase() = default; virtual SWriterInfo GetInfo() = 0; - virtual bool Create(const std::string& host_name_, const std::string& topic_name_, const std::string & topic_id_) = 0; - virtual bool Destroy() = 0; - virtual void AddLocConnection(const std::string& /*process_id_*/, const std::string& /*topic_id_*/, const std::string& /*conn_par_*/) {}; virtual void RemLocConnection(const std::string& /*process_id_*/, const std::string& /*topic_id_*/) {}; @@ -62,7 +58,5 @@ namespace eCAL std::string m_host_name; std::string m_topic_name; std::string m_topic_id; - - std::atomic m_created; }; } diff --git a/ecal/core/src/readwrite/shm/ecal_writer_shm.cpp b/ecal/core/src/readwrite/shm/ecal_writer_shm.cpp index 9ee4cd48df..ca8484d647 100644 --- a/ecal/core/src/readwrite/shm/ecal_writer_shm.cpp +++ b/ecal/core/src/readwrite/shm/ecal_writer_shm.cpp @@ -33,34 +33,10 @@ namespace eCAL { const std::string CDataWriterSHM::m_memfile_base_name = "ecal_"; - CDataWriterSHM::~CDataWriterSHM() + CDataWriterSHM::CDataWriterSHM(const std::string& host_name_, const std::string& topic_name_, const std::string& topic_id_) { - Destroy(); - } - - SWriterInfo CDataWriterSHM::GetInfo() - { - SWriterInfo info_; - - info_.name = "shm"; - info_.description = "Local shared memory data writer"; - - info_.has_mode_local = true; - info_.has_mode_cloud = false; - - info_.send_size_max = -1; - - return info_; - } - - bool CDataWriterSHM::Create(const std::string& /*host_name_*/, const std::string& topic_name_, const std::string & /*topic_id_*/) - { - if (m_created) return true; m_topic_name = topic_name_; - // init write index and create memory files - m_write_idx = 0; - // set attributes m_memory_file_attr.min_size = Config::GetMemfileMinsizeBytes(); m_memory_file_attr.reserve = Config::GetMemfileOverprovisioningPercentage(); @@ -68,21 +44,24 @@ namespace eCAL m_memory_file_attr.timeout_ack_ms = Config::GetMemfileAckTimeoutMs(); // initialize memory file buffer - m_created = SetBufferCount(m_buffer_count); - - return m_created; + SetBufferCount(m_buffer_count /*= 1*/); } - bool CDataWriterSHM::Destroy() + SWriterInfo CDataWriterSHM::GetInfo() { - if (!m_created) return true; - m_created = false; + SWriterInfo info_; - m_memory_file_vec.clear(); + info_.name = "shm"; + info_.description = "Local shared memory data writer"; - return true; - } + info_.has_mode_local = true; + info_.has_mode_cloud = false; + + info_.send_size_max = -1; + return info_; + } + bool CDataWriterSHM::SetBufferCount(size_t buffer_count_) { // no need to adapt anything @@ -128,8 +107,6 @@ namespace eCAL bool CDataWriterSHM::PrepareWrite(const SWriterAttr& attr_) { - if (!m_created) return false; - // false signals no rematching / exchanging of // connection parameters needed bool ret_state(false); @@ -155,8 +132,6 @@ namespace eCAL bool CDataWriterSHM::Write(CPayloadWriter& payload_, const SWriterAttr& attr_) { - if (!m_created) return false; - // write content const bool force_full_write(m_memory_file_vec.size() > 1); const bool sent = m_memory_file_vec[m_write_idx]->Write(payload_, attr_, force_full_write); @@ -170,8 +145,6 @@ namespace eCAL void CDataWriterSHM::AddLocConnection(const std::string& process_id_, const std::string& /*topic_id_*/, const std::string& /*conn_par_*/) { - if (!m_created) return; - for (auto& memory_file : m_memory_file_vec) { memory_file->Connect(process_id_); diff --git a/ecal/core/src/readwrite/shm/ecal_writer_shm.h b/ecal/core/src/readwrite/shm/ecal_writer_shm.h index d3081a5aa9..ddc5f4bc46 100644 --- a/ecal/core/src/readwrite/shm/ecal_writer_shm.h +++ b/ecal/core/src/readwrite/shm/ecal_writer_shm.h @@ -36,16 +36,10 @@ namespace eCAL class CDataWriterSHM : public CDataWriterBase { public: - CDataWriterSHM() = default; - ~CDataWriterSHM() override; + CDataWriterSHM(const std::string& host_name_, const std::string& topic_name_, const std::string& topic_id_); SWriterInfo GetInfo() override; - bool Create(const std::string& host_name_, const std::string& topic_name_, const std::string & topic_id_) override; - // this virtual function is called during construction/destruction, - // so, mark it as final to ensure that no derived classes override it. - bool Destroy() final; - bool SetBufferCount(size_t buffer_count_); bool PrepareWrite(const SWriterAttr& attr_) override; diff --git a/ecal/core/src/readwrite/tcp/ecal_writer_tcp.cpp b/ecal/core/src/readwrite/tcp/ecal_writer_tcp.cpp index 59dabf9586..85bb6234ab 100644 --- a/ecal/core/src/readwrite/tcp/ecal_writer_tcp.cpp +++ b/ecal/core/src/readwrite/tcp/ecal_writer_tcp.cpp @@ -21,11 +21,11 @@ * @brief tcp writer **/ +#include + #include "config/ecal_config_reader_hlp.h" #include "serialization/ecal_serialize_sample_payload.h" -#include - #include "ecal_writer_tcp.h" #include "ecal_tcp_pubsub_logger.h" @@ -38,31 +38,7 @@ namespace eCAL std::mutex CDataWriterTCP::g_tcp_writer_executor_mtx; std::shared_ptr CDataWriterTCP::g_tcp_writer_executor; - CDataWriterTCP::CDataWriterTCP() : m_port(0) - { - } - - CDataWriterTCP::~CDataWriterTCP() - { - Destroy(); - } - - SWriterInfo CDataWriterTCP::GetInfo() - { - SWriterInfo info_; - - info_.name = "tcp"; - info_.description = "tcp data writer"; - - info_.has_mode_local = true; - info_.has_mode_cloud = true; - - info_.send_size_max = -1; - - return info_; - } - - bool CDataWriterTCP::Create(const std::string& host_name_, const std::string& topic_name_, const std::string& topic_id_) + CDataWriterTCP::CDataWriterTCP(const std::string& host_name_, const std::string& topic_name_, const std::string& topic_id_) { { const std::lock_guard lock(g_tcp_writer_executor_mtx); @@ -80,19 +56,21 @@ namespace eCAL m_host_name = host_name_; m_topic_name = topic_name_; m_topic_id = topic_id_; - - return true; } - bool CDataWriterTCP::Destroy() + SWriterInfo CDataWriterTCP::GetInfo() { - if(!m_publisher) return true; + SWriterInfo info_; - // destroy publisher - m_publisher = nullptr; - m_port = 0; + info_.name = "tcp"; + info_.description = "tcp data writer"; - return true; + info_.has_mode_local = true; + info_.has_mode_cloud = true; + + info_.send_size_max = -1; + + return info_; } bool CDataWriterTCP::Write(const void* const buf_, const SWriterAttr& attr_) diff --git a/ecal/core/src/readwrite/tcp/ecal_writer_tcp.h b/ecal/core/src/readwrite/tcp/ecal_writer_tcp.h index ee90ff62c5..bd2cc4d66e 100644 --- a/ecal/core/src/readwrite/tcp/ecal_writer_tcp.h +++ b/ecal/core/src/readwrite/tcp/ecal_writer_tcp.h @@ -38,16 +38,10 @@ namespace eCAL class CDataWriterTCP : public CDataWriterBase { public: - CDataWriterTCP(); - ~CDataWriterTCP() override; + CDataWriterTCP(const std::string& host_name_, const std::string& topic_name_, const std::string& topic_id_); SWriterInfo GetInfo() override; - bool Create(const std::string& host_name_, const std::string& topic_name_, const std::string & topic_id_) override; - // this virtual function is called during construction/destruction, - // so, mark it as final to ensure that no derived classes override it. - bool Destroy() final; - bool Write(const void* buf_, const SWriterAttr& attr_) override; Registration::ConnectionPar GetConnectionParameter() override; @@ -59,6 +53,6 @@ namespace eCAL static std::shared_ptr g_tcp_writer_executor; std::shared_ptr m_publisher; - uint16_t m_port; + uint16_t m_port = 0; }; } diff --git a/ecal/core/src/readwrite/udp/ecal_writer_udp_mc.cpp b/ecal/core/src/readwrite/udp/ecal_writer_udp_mc.cpp index a53aba4408..60055517d3 100644 --- a/ecal/core/src/readwrite/udp/ecal_writer_udp_mc.cpp +++ b/ecal/core/src/readwrite/udp/ecal_writer_udp_mc.cpp @@ -21,42 +21,19 @@ * @brief udp data writer **/ -#include #include #include -#include -#include #include "ecal_writer_udp_mc.h" #include "io/udp/ecal_udp_configurations.h" #include "serialization/ecal_serialize_sample_payload.h" +#include + namespace eCAL { - CDataWriterUdpMC::~CDataWriterUdpMC() - { - Destroy(); - } - - SWriterInfo CDataWriterUdpMC::GetInfo() - { - SWriterInfo info_; - - info_.name = "udp"; - info_.description = "udp multicast data writer"; - - info_.has_mode_local = true; - info_.has_mode_cloud = true; - - info_.send_size_max = -1; - - return info_; - } - - bool CDataWriterUdpMC::Create(const std::string & host_name_, const std::string & topic_name_, const std::string & topic_id_) + CDataWriterUdpMC::CDataWriterUdpMC(const std::string& host_name_, const std::string& topic_name_, const std::string& topic_id_) { - if (m_created) return true; - m_host_name = host_name_; m_topic_name = topic_name_; m_topic_id = topic_id_; @@ -76,26 +53,25 @@ namespace eCAL // create udp/sample sender without activated loop-back attr.loopback = false; m_sample_sender_no_loopback = std::make_shared(attr); - - m_created = true; - return true; } - bool CDataWriterUdpMC::Destroy() + SWriterInfo CDataWriterUdpMC::GetInfo() { - if (!m_created) return true; + SWriterInfo info_; - m_sample_sender_loopback.reset(); - m_sample_sender_no_loopback.reset(); + info_.name = "udp"; + info_.description = "udp multicast data writer"; + + info_.has_mode_local = true; + info_.has_mode_cloud = true; - m_created = false; - return true; + info_.send_size_max = -1; + + return info_; } bool CDataWriterUdpMC::Write(const void* const buf_, const SWriterAttr& attr_) { - if (!m_created) return false; - // create new sample Payload::Sample ecal_sample; ecal_sample.cmd_type = eCmdType::bct_set_sample; diff --git a/ecal/core/src/readwrite/udp/ecal_writer_udp_mc.h b/ecal/core/src/readwrite/udp/ecal_writer_udp_mc.h index 9b9df24207..fc7d820ae2 100644 --- a/ecal/core/src/readwrite/udp/ecal_writer_udp_mc.h +++ b/ecal/core/src/readwrite/udp/ecal_writer_udp_mc.h @@ -35,15 +35,10 @@ namespace eCAL class CDataWriterUdpMC : public CDataWriterBase { public: - ~CDataWriterUdpMC() override; + CDataWriterUdpMC(const std::string& host_name_, const std::string& topic_name_, const std::string& topic_id_); SWriterInfo GetInfo() override; - bool Create(const std::string& host_name_, const std::string& topic_name_, const std::string & topic_id_) override; - // this virtual function is called during construction/destruction, - // so, mark it as final to ensure that no derived classes override it. - bool Destroy() final; - bool Write(const void* buf_, const SWriterAttr& attr_) override; protected: From 2116e5f11e4140f865d17699ebd41adbb7a76ff9 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:27:43 +0200 Subject: [PATCH 05/11] [Core Tests] Added missing include (#1575) --- ecal/tests/cpp/util_test/src/util_gettopics.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ecal/tests/cpp/util_test/src/util_gettopics.cpp b/ecal/tests/cpp/util_test/src/util_gettopics.cpp index 2665df9924..112234d018 100644 --- a/ecal/tests/cpp/util_test/src/util_gettopics.cpp +++ b/ecal/tests/cpp/util_test/src/util_gettopics.cpp @@ -21,6 +21,7 @@ #include +#include #include #include #include From c0e30492298d1ca6f24159a238dac74fcefac945 Mon Sep 17 00:00:00 2001 From: Leon H <140434610+LeonHosch@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:35:27 +0200 Subject: [PATCH 06/11] [Python] Added BinarySubscriber and BinaryPublisher with examples (#1526) Co-authored-by: Leon Hosch --- lang/python/core/ecal/core/publisher.py | 12 +++++ lang/python/core/ecal/core/subscriber.py | 45 ++++++++++++++++ samples/CMakeLists.txt | 3 ++ .../pubsub/binary/binary_rec/CMakeLists.txt | 32 +++++++++++ .../pubsub/binary/binary_rec/binary_rec.py | 49 +++++++++++++++++ .../binary/binary_rec/binary_rec.pyproj | 35 ++++++++++++ .../binary/binary_rec_cb/CMakeLists.txt | 32 +++++++++++ .../binary/binary_rec_cb/binary_rec_cb.py | 52 ++++++++++++++++++ .../binary/binary_rec_cb/binary_rec_cb.pyproj | 35 ++++++++++++ .../pubsub/binary/binary_snd/CMakeLists.txt | 32 +++++++++++ .../pubsub/binary/binary_snd/binary_snd.py | 54 +++++++++++++++++++ .../binary/binary_snd/binary_snd.pyproj | 35 ++++++++++++ 12 files changed, 416 insertions(+) create mode 100644 samples/python/pubsub/binary/binary_rec/CMakeLists.txt create mode 100644 samples/python/pubsub/binary/binary_rec/binary_rec.py create mode 100644 samples/python/pubsub/binary/binary_rec/binary_rec.pyproj create mode 100644 samples/python/pubsub/binary/binary_rec_cb/CMakeLists.txt create mode 100644 samples/python/pubsub/binary/binary_rec_cb/binary_rec_cb.py create mode 100644 samples/python/pubsub/binary/binary_rec_cb/binary_rec_cb.pyproj create mode 100644 samples/python/pubsub/binary/binary_snd/CMakeLists.txt create mode 100644 samples/python/pubsub/binary/binary_snd/binary_snd.py create mode 100644 samples/python/pubsub/binary/binary_snd/binary_snd.pyproj diff --git a/lang/python/core/ecal/core/publisher.py b/lang/python/core/ecal/core/publisher.py index 8f341dfaa8..3e870080a4 100644 --- a/lang/python/core/ecal/core/publisher.py +++ b/lang/python/core/ecal/core/publisher.py @@ -65,6 +65,18 @@ def __init__(self, name, type_=None): def send(self, msg, time=-1): return self.c_publisher.send(msg.SerializeToString(), time) +class BinaryPublisher(MessagePublisher): + """Spezialized publisher that sends out binary messages + """ + def __init__(self, name): + topic_type = "binary" + topic_enc = "base" + topic_desc = b"" + super(BinaryPublisher, self).__init__(name, topic_type, topic_enc, topic_desc) + + def send(self, msg, time=-1): + return self.c_publisher.send(msg, time) + class StringPublisher(MessagePublisher): """Spezialized publisher that sends out plain strings """ diff --git a/lang/python/core/ecal/core/subscriber.py b/lang/python/core/ecal/core/subscriber.py index f6ca858f02..4011a2a82c 100644 --- a/lang/python/core/ecal/core/subscriber.py +++ b/lang/python/core/ecal/core/subscriber.py @@ -117,6 +117,51 @@ def _on_receive(self, topic_name, msg, time): self.callback(topic_name, proto_message, time) +class BinarySubscriber(MessageSubscriber): + """Specialized subscriber that subscribes to binary messages + """ + def __init__(self, name): + topic_type = "binary" + topic_enc = "base" + topic_desc = b"" + super(BinarySubscriber, self).__init__(name, topic_type, topic_enc, topic_desc) + self.callback = None + + def receive(self, timeout=0): + """ receive subscriber content with timeout + + :param timeout: receive timeout in ms + + """ + ret, msg, time = self.c_subscriber.receive(timeout) + if ret > 0: + msg = msg + else: + msg = b"" + return ret, msg, time + + def set_callback(self, callback): + """ set callback function for incoming messages + + :param callback: python callback function (f(topic_name, msg, time)) + + """ + self.callback = callback + self.c_subscriber.set_callback(self._on_receive) + + def rem_callback(self, callback): + """ remove callback function for incoming messages + + :param callback: python callback function (f(topic_name, msg, time)) + + """ + self.c_subscriber.rem_callback(self._on_receive) + self.callback = None + + def _on_receive(self, topic_name, msg, time): + self.callback(topic_name, msg, time) + + class StringSubscriber(MessageSubscriber): """Spezialized publisher subscribes to plain strings """ diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index dca119e633..22f0b515e8 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -73,6 +73,9 @@ if(BUILD_PY_BINDING) add_subdirectory(python/pubsub/string/minimal_rec) add_subdirectory(python/pubsub/string/minimal_rec_cb) add_subdirectory(python/pubsub/string/minimal_snd) + add_subdirectory(python/pubsub/binary/binary_rec) + add_subdirectory(python/pubsub/binary/binary_rec_cb) + add_subdirectory(python/pubsub/binary/binary_snd) # services add_subdirectory(python/services/minimal_service) diff --git a/samples/python/pubsub/binary/binary_rec/CMakeLists.txt b/samples/python/pubsub/binary/binary_rec/CMakeLists.txt new file mode 100644 index 0000000000..181e6cbaa3 --- /dev/null +++ b/samples/python/pubsub/binary/binary_rec/CMakeLists.txt @@ -0,0 +1,32 @@ +# ========================= eCAL LICENSE ================================= +# +# Copyright (C) 2016 - 2019 Continental Corporation +# +# 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. +# +# ========================= eCAL LICENSE ================================= + +project(binary_rec) + +find_package(eCAL REQUIRED) + +set(PROJECT_GROUP binary) + +if(ECAL_INCLUDE_PY_SAMPLES) + if(WIN32) + + include_external_msproject(${PROJECT_NAME}_py ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.pyproj) + set_property(TARGET ${PROJECT_NAME}_py PROPERTY FOLDER samples/python/${PROJECT_GROUP}) + + endif() +endif() \ No newline at end of file diff --git a/samples/python/pubsub/binary/binary_rec/binary_rec.py b/samples/python/pubsub/binary/binary_rec/binary_rec.py new file mode 100644 index 0000000000..8e2041b0b9 --- /dev/null +++ b/samples/python/pubsub/binary/binary_rec/binary_rec.py @@ -0,0 +1,49 @@ + # ========================= eCAL LICENSE ================================= +# +# Copyright (C) 2016 - 2019 Continental Corporation +# +# 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. +# +# ========================= eCAL LICENSE ================================= + +import sys + +import ecal.core.core as ecal_core +from ecal.core.subscriber import BinarySubscriber + +def main(): + # print eCAL version and date + print("eCAL {} ({})\n".format(ecal_core.getversion(), ecal_core.getdate())) + + # initialize eCAL API + ecal_core.initialize(sys.argv, "py_binary_rec") + + # set process state + ecal_core.set_process_state(1, 1, "I feel good") + + # create subscriber + sub = BinarySubscriber("Hello") + + # receive messages + while ecal_core.ok(): + ret, msg, time = sub.receive(500) + if ret > 0: + print("Received: {} ms {}".format(time, bytes.fromhex(msg.decode("utf-8")))) + else: + print("Subscriber timeout ..") + + # finalize eCAL API + ecal_core.finalize() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/samples/python/pubsub/binary/binary_rec/binary_rec.pyproj b/samples/python/pubsub/binary/binary_rec/binary_rec.pyproj new file mode 100644 index 0000000000..31855265cf --- /dev/null +++ b/samples/python/pubsub/binary/binary_rec/binary_rec.pyproj @@ -0,0 +1,35 @@ + + + + Debug + 2.0 + . + binary_rec.py + ..\..\..\lang\python\src + . + . + binary_rec + binary_rec + {093990a3-9395-3bb2-b625-473f1bba2ce6} + + + true + false + + + true + false + + + + + + 10.0 + + + + + + + + \ No newline at end of file diff --git a/samples/python/pubsub/binary/binary_rec_cb/CMakeLists.txt b/samples/python/pubsub/binary/binary_rec_cb/CMakeLists.txt new file mode 100644 index 0000000000..2887d4a544 --- /dev/null +++ b/samples/python/pubsub/binary/binary_rec_cb/CMakeLists.txt @@ -0,0 +1,32 @@ +# ========================= eCAL LICENSE ================================= +# +# Copyright (C) 2016 - 2019 Continental Corporation +# +# 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. +# +# ========================= eCAL LICENSE ================================= + +project(binary_rec_cb) + +find_package(eCAL REQUIRED) + +set(PROJECT_GROUP binary) + +if(ECAL_INCLUDE_PY_SAMPLES) + if(WIN32) + + include_external_msproject(${PROJECT_NAME}_py ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.pyproj) + set_property(TARGET ${PROJECT_NAME}_py PROPERTY FOLDER samples/python/${PROJECT_GROUP}) + + endif() +endif() diff --git a/samples/python/pubsub/binary/binary_rec_cb/binary_rec_cb.py b/samples/python/pubsub/binary/binary_rec_cb/binary_rec_cb.py new file mode 100644 index 0000000000..546c8cc1c2 --- /dev/null +++ b/samples/python/pubsub/binary/binary_rec_cb/binary_rec_cb.py @@ -0,0 +1,52 @@ +# ========================= eCAL LICENSE ================================= +# +# Copyright (C) 2016 - 2019 Continental Corporation +# +# 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. +# +# ========================= eCAL LICENSE ================================= + +import sys +import time + +import ecal.core.core as ecal_core +from ecal.core.subscriber import BinarySubscriber + +# eCAL receive callback +def callback(topic_name, msg, time): + print("Received: {} ms {}".format(time, bytes.fromhex(msg.decode("utf-8")))) + +def main(): + # print eCAL version and date + print("eCAL {} ({})\n".format(ecal_core.getversion(), ecal_core.getdate())) + + # initialize eCAL API + ecal_core.initialize(sys.argv, "py_binary_rec_cb") + + # set process state + ecal_core.set_process_state(1, 1, "I feel good") + + # create subscriber and connect callback + sub = BinarySubscriber("Hello") + sub.set_callback(callback) + + # idle main thread + while ecal_core.ok(): + time.sleep(0.1) + + # finalize eCAL API + ecal_core.finalize() + +if __name__ == "__main__": + main() + diff --git a/samples/python/pubsub/binary/binary_rec_cb/binary_rec_cb.pyproj b/samples/python/pubsub/binary/binary_rec_cb/binary_rec_cb.pyproj new file mode 100644 index 0000000000..5478851607 --- /dev/null +++ b/samples/python/pubsub/binary/binary_rec_cb/binary_rec_cb.pyproj @@ -0,0 +1,35 @@ + + + + Debug + 2.0 + . + binary_rec_cb.py + ..\..\..\lang\python\src + . + . + binary_rec_cb + binary_rec_cb + {a38cc1fc-76f7-3172-81ac-b754dc0f9d60} + + + true + false + + + true + false + + + + + + 10.0 + + + + + + + + \ No newline at end of file diff --git a/samples/python/pubsub/binary/binary_snd/CMakeLists.txt b/samples/python/pubsub/binary/binary_snd/CMakeLists.txt new file mode 100644 index 0000000000..bdc0dec522 --- /dev/null +++ b/samples/python/pubsub/binary/binary_snd/CMakeLists.txt @@ -0,0 +1,32 @@ +# ========================= eCAL LICENSE ================================= +# +# Copyright (C) 2016 - 2019 Continental Corporation +# +# 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. +# +# ========================= eCAL LICENSE ================================= + +project(binary_snd) + +find_package(eCAL REQUIRED) + +set(PROJECT_GROUP binary) + +if(ECAL_INCLUDE_PY_SAMPLES) + if(WIN32) + + include_external_msproject(${PROJECT_NAME}_py ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.pyproj) + set_property(TARGET ${PROJECT_NAME}_py PROPERTY FOLDER samples/python/${PROJECT_GROUP}) + + endif() +endif() \ No newline at end of file diff --git a/samples/python/pubsub/binary/binary_snd/binary_snd.py b/samples/python/pubsub/binary/binary_snd/binary_snd.py new file mode 100644 index 0000000000..914765d488 --- /dev/null +++ b/samples/python/pubsub/binary/binary_snd/binary_snd.py @@ -0,0 +1,54 @@ +# ========================= eCAL LICENSE ================================= +# +# Copyright (C) 2016 - 2019 Continental Corporation +# +# 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. +# +# ========================= eCAL LICENSE ================================= + +import sys +import time +import random + +import ecal.core.core as ecal_core +from ecal.core.publisher import BinaryPublisher + +def main(): + # print eCAL version and date + print("eCAL {} ({})\n".format(ecal_core.getversion(), ecal_core.getdate())) + + # initialize eCAL API + ecal_core.initialize(sys.argv, "py_binary_snd") + + # set process state + ecal_core.set_process_state(1, 1, "I feel good") + + # create publisher + pub = BinaryPublisher("Hello") + msg_fox = b"4120717569636b2062726f776e20666f7820" + + # send messages + msg_count = 0 + while ecal_core.ok(): + msg_count += 1 + hex_ascii = bytes(''.join([hex(ord(digit))[2:] for digit in str(msg_count)]), "utf-8") + msg = msg_fox + hex_ascii + pub.send(msg) + print("Sent: ", msg) + time.sleep(0.01) + + # finalize eCAL API + ecal_core.finalize() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/samples/python/pubsub/binary/binary_snd/binary_snd.pyproj b/samples/python/pubsub/binary/binary_snd/binary_snd.pyproj new file mode 100644 index 0000000000..c7223cb68c --- /dev/null +++ b/samples/python/pubsub/binary/binary_snd/binary_snd.pyproj @@ -0,0 +1,35 @@ + + + + Debug + 2.0 + . + binary_snd.py + ..\..\..\lang\python\src + . + . + binary_snd + binary_snd + {5ebed1e0-8867-3120-bbfe-6d6db73e8077} + + + true + false + + + true + false + + + + + + 10.0 + + + + + + + + \ No newline at end of file From a65a831e487636bfe5131a685bea3cc6ca52cc7d Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Fri, 3 May 2024 08:30:39 +0200 Subject: [PATCH 07/11] [Core / Service]: Fixed crash that occured when communicating with eCAL 5.12 services (#1579) The crash occured when a v0 client connected to a service server. --- .../src/server_session_impl_v0.cpp | 12 ++ .../ecal_service/src/server_session_impl_v0.h | 5 + .../test/src/ecal_tcp_service_test.cpp | 145 ++++++++++++++++++ 3 files changed, 162 insertions(+) diff --git a/ecal/service/ecal_service/src/server_session_impl_v0.cpp b/ecal/service/ecal_service/src/server_session_impl_v0.cpp index bb984839e0..d06dfe050c 100644 --- a/ecal/service/ecal_service/src/server_session_impl_v0.cpp +++ b/ecal/service/ecal_service/src/server_session_impl_v0.cpp @@ -74,6 +74,18 @@ namespace eCAL // Data receiving and sending /////////////////////////////////////////////// void ServerSessionV0::start() + { + // Call the handle_start with the io_service + // It is important to async call handle_start(), as it will call a + // user-defined callback. As we have no influence what that callback will + // be, we must call it from another thread to make sure to not double-lock + // mutexes from the server_impl, if the callback should itself call a + // server_impl api function. + + io_context_->post([me = shared_from_this()]() { me->handle_start(); }); + } + + void ServerSessionV0::handle_start() { // Go to handshake state state_ = State::CONNECTED; diff --git a/ecal/service/ecal_service/src/server_session_impl_v0.h b/ecal/service/ecal_service/src/server_session_impl_v0.h index cffeda48a0..4c4f93dbe8 100644 --- a/ecal/service/ecal_service/src/server_session_impl_v0.h +++ b/ecal/service/ecal_service/src/server_session_impl_v0.h @@ -75,6 +75,11 @@ namespace eCAL /////////////////////////////////////////////// public: void start() override; + + private: + void handle_start(); + + public: void stop() override; eCAL::service::State get_state() const override; diff --git a/ecal/service/test/src/ecal_tcp_service_test.cpp b/ecal/service/test/src/ecal_tcp_service_test.cpp index 0683ed708c..67455b54d4 100644 --- a/ecal/service/test/src/ecal_tcp_service_test.cpp +++ b/ecal/service/test/src/ecal_tcp_service_test.cpp @@ -1605,6 +1605,151 @@ TEST(ecal_service, Callback_SerializedServiceCallbacks) // NOLINT } #endif +#if 1 +// Call different eCAL Service API functions from within the callbacks +TEST(ecal_service, Callback_ApiCallsFromCallbacks) +{ + for (std::uint8_t protocol_version = min_protocol_version; protocol_version <= max_protocol_version; protocol_version++) + { + const auto io_context = std::make_shared(); + const asio::io_context::work dummy_work(*io_context); + + atomic_signalable num_server_service_callback_called (0); + atomic_signalable num_server_event_callback_called (0); + atomic_signalable num_client_response_callback_called(0); + atomic_signalable num_client_event_callback_called (0); + + // declare server and client shared_ptrs as we need those in the callbacks. + std::shared_ptr server; + std::shared_ptr client; + + const eCAL::service::Server::ServiceCallbackT server_service_callback + = [&num_server_service_callback_called, &server](const std::shared_ptr& /*request*/, const std::shared_ptr& /*response*/) -> void + { + if (server) + { + bool is_connected = server->is_connected(); + int connection_count = server->get_connection_count(); + uint16_t port = server->get_port(); + + EXPECT_EQ(is_connected, true); + EXPECT_EQ(connection_count, 1); + } + + num_server_service_callback_called++; + }; + + const eCAL::service::Server::EventCallbackT server_event_callback + = [&num_server_event_callback_called, &server](eCAL::service::ServerEventType event, const std::string& /*message*/) -> void + { + if (server) + { + bool is_connected = server->is_connected(); + int connection_count = server->get_connection_count(); + uint16_t port = server->get_port(); + + if (event == eCAL::service::ServerEventType::Connected) + { + ASSERT_EQ(is_connected, true); + ASSERT_EQ(connection_count, 1); + } + else if (event == eCAL::service::ServerEventType::Disconnected) + { + ASSERT_EQ(is_connected, false); + ASSERT_EQ(connection_count, 0); + } + } + + num_server_event_callback_called++; + }; + + const eCAL::service::ClientSession::ResponseCallbackT client_response_callback + = [&num_client_response_callback_called, &client] + (const eCAL::service::Error& error, const std::shared_ptr& /*response*/) -> void + { + if(client) + { + // We just test if those functions can be called without crashing + auto address = client->get_address(); + auto port = client->get_port(); + auto protocol_version = client->get_accepted_protocol_version(); + auto queue_size = client->get_queue_size(); + auto state = client->get_state(); + } + + num_client_response_callback_called++; + }; + + const eCAL::service::ClientSession::EventCallbackT client_event_callback + = [&num_client_event_callback_called, &client] + (eCAL::service::ClientEventType event, const std::string& /*message*/) -> void + { + if (client) + { + // We just test if those functions can be called without crashing + auto address = client->get_address(); + auto port = client->get_port(); + auto protocol_version = client->get_accepted_protocol_version(); + auto queue_size = client->get_queue_size(); + auto state = client->get_state(); + } + + num_client_event_callback_called++; + }; + + server = eCAL::service::Server::create(io_context, protocol_version, 0, server_service_callback, true, server_event_callback); + + EXPECT_EQ(num_server_service_callback_called.get(), 0); + EXPECT_EQ(num_server_event_callback_called.get(), 0); + EXPECT_EQ(num_client_response_callback_called.get(), 0); + EXPECT_EQ(num_client_event_callback_called.get(), 0); + + client = eCAL::service::ClientSession::create(io_context, protocol_version, "127.0.0.1", server->get_port(), client_event_callback); + + std::thread io_thread([&io_context]() + { + io_context->run(); + }); + + // Wait for the connected events to be called + num_server_event_callback_called.wait_for([](int value) { return value >= 1; }, std::chrono::milliseconds(500)); + num_client_event_callback_called.wait_for([](int value) { return value >= 1; }, std::chrono::milliseconds(500)); + + EXPECT_EQ(num_server_service_callback_called.get(), 0); + EXPECT_EQ(num_server_event_callback_called.get(), 1); + EXPECT_EQ(num_client_response_callback_called.get(), 0); + EXPECT_EQ(num_client_event_callback_called.get(), 1); + + // Call service and wait for the response + client->async_call_service(std::make_shared("1"), client_response_callback); + + num_server_service_callback_called .wait_for([](int value) { return value >= 1; }, std::chrono::milliseconds(500)); + num_client_response_callback_called.wait_for([](int value) { return value >= 1; }, std::chrono::milliseconds(500)); + + EXPECT_EQ(num_server_service_callback_called.get(), 1); + EXPECT_EQ(num_server_event_callback_called.get(), 1); + EXPECT_EQ(num_client_response_callback_called.get(), 1); + EXPECT_EQ(num_client_event_callback_called.get(), 1); + + // Terminate the client + client = nullptr; + + // Wait for the disconnected events to be called + num_server_event_callback_called.wait_for([](int value) { return value >= 2; }, std::chrono::milliseconds(500)); + + EXPECT_EQ(num_server_service_callback_called.get(), 1); + EXPECT_EQ(num_server_event_callback_called.get(), 2); + + // Terminate the server + server = nullptr; + + // join the io_thread + io_context->stop(); + io_thread.join(); + } +} +#endif + #if 1 TEST(ecal_service, ErrorCallback_ErrorCallbackNoServer) // NOLINT { From 6a4bf8b3fcd759b7287ba1df22ac6f9c13c32716 Mon Sep 17 00:00:00 2001 From: KerstinKeller Date: Tue, 7 May 2024 09:21:08 +0200 Subject: [PATCH 08/11] [build] reactivate capnproto and flatbuffers sapmles (#1585) --- samples/CMakeLists.txt | 12 ++++++++++++ .../cpp/pubsub/capnp/addressbook_rec/CMakeLists.txt | 2 +- .../pubsub/capnp/addressbook_rec_cb/CMakeLists.txt | 2 +- .../capnp/addressbook_rec_dynamic/CMakeLists.txt | 2 +- .../cpp/pubsub/capnp/addressbook_snd/CMakeLists.txt | 2 +- .../cpp/pubsub/flatbuffer/monster_rec/CMakeLists.txt | 2 +- .../cpp/pubsub/flatbuffer/monster_snd/CMakeLists.txt | 2 +- 7 files changed, 18 insertions(+), 6 deletions(-) diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 22f0b515e8..ad8b6312be 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -33,6 +33,18 @@ add_subdirectory(cpp/measurement/measurement_read) add_subdirectory(cpp/measurement/measurement_write) endif(HAS_HDF5) +if (HAS_CAPNPROTO) +add_subdirectory(cpp/pubsub/capnp/addressbook_rec) +add_subdirectory(cpp/pubsub/capnp/addressbook_rec_cb) +add_subdirectory(cpp/pubsub/capnp/addressbook_rec_dynamic) +add_subdirectory(cpp/pubsub/capnp/addressbook_snd) +endif(HAS_CAPNPROTO) + +if (HAS_FLATBUFFERS) +add_subdirectory(cpp/pubsub/flatbuffer/monster_rec) +add_subdirectory(cpp/pubsub/flatbuffer/monster_snd) +endif(HAS_FLATBUFFERS) + # services add_subdirectory(cpp/services/ecalplayer_client) if(HAS_QT) diff --git a/samples/cpp/pubsub/capnp/addressbook_rec/CMakeLists.txt b/samples/cpp/pubsub/capnp/addressbook_rec/CMakeLists.txt index bc91ac896c..28cf9e8dfd 100644 --- a/samples/cpp/pubsub/capnp/addressbook_rec/CMakeLists.txt +++ b/samples/cpp/pubsub/capnp/addressbook_rec/CMakeLists.txt @@ -38,4 +38,4 @@ target_link_options(${PROJECT_NAME} PRIVATE $<$:/ignore:40 ecal_install_sample(${PROJECT_NAME}) -set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER samples/cpp/pubsub/capnp/addressbook) +set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER samples/cpp/pubsub/capnproto) diff --git a/samples/cpp/pubsub/capnp/addressbook_rec_cb/CMakeLists.txt b/samples/cpp/pubsub/capnp/addressbook_rec_cb/CMakeLists.txt index 8b6ec95a6f..7719484ef9 100644 --- a/samples/cpp/pubsub/capnp/addressbook_rec_cb/CMakeLists.txt +++ b/samples/cpp/pubsub/capnp/addressbook_rec_cb/CMakeLists.txt @@ -38,4 +38,4 @@ target_link_options(${PROJECT_NAME} PRIVATE $<$:/ignore:40 ecal_install_sample(${PROJECT_NAME}) -set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER samples/cpp/pubsub/capnp/addressbook) \ No newline at end of file +set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER samples/cpp/pubsub/capnproto) \ No newline at end of file diff --git a/samples/cpp/pubsub/capnp/addressbook_rec_dynamic/CMakeLists.txt b/samples/cpp/pubsub/capnp/addressbook_rec_dynamic/CMakeLists.txt index 0af1ddd0e7..cdb1817eeb 100644 --- a/samples/cpp/pubsub/capnp/addressbook_rec_dynamic/CMakeLists.txt +++ b/samples/cpp/pubsub/capnp/addressbook_rec_dynamic/CMakeLists.txt @@ -34,4 +34,4 @@ target_link_options(${PROJECT_NAME} PRIVATE $<$:/ignore:40 ecal_install_sample(${PROJECT_NAME}) -set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER samples/cpp/pubsub/capnp/addressbook) +set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER samples/cpp/pubsub/capnproto) diff --git a/samples/cpp/pubsub/capnp/addressbook_snd/CMakeLists.txt b/samples/cpp/pubsub/capnp/addressbook_snd/CMakeLists.txt index af7f523293..89a7571036 100644 --- a/samples/cpp/pubsub/capnp/addressbook_snd/CMakeLists.txt +++ b/samples/cpp/pubsub/capnp/addressbook_snd/CMakeLists.txt @@ -38,4 +38,4 @@ target_link_options(${PROJECT_NAME} PRIVATE $<$:/ignore:40 ecal_install_sample(${PROJECT_NAME}) -set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER samples/cpp/pubsub/capnp/addressbook) +set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER samples/cpp/pubsub/capnproto) diff --git a/samples/cpp/pubsub/flatbuffer/monster_rec/CMakeLists.txt b/samples/cpp/pubsub/flatbuffer/monster_rec/CMakeLists.txt index 1501355991..65f7e5d1f5 100644 --- a/samples/cpp/pubsub/flatbuffer/monster_rec/CMakeLists.txt +++ b/samples/cpp/pubsub/flatbuffer/monster_rec/CMakeLists.txt @@ -45,4 +45,4 @@ target_link_libraries(${PROJECT_NAME} ecal_install_sample(${PROJECT_NAME}) -set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER samples/cpp/monster) \ No newline at end of file +set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER samples/cpp/pubsub/flatbuffers) \ No newline at end of file diff --git a/samples/cpp/pubsub/flatbuffer/monster_snd/CMakeLists.txt b/samples/cpp/pubsub/flatbuffer/monster_snd/CMakeLists.txt index 8e61aec451..cde6831471 100644 --- a/samples/cpp/pubsub/flatbuffer/monster_snd/CMakeLists.txt +++ b/samples/cpp/pubsub/flatbuffer/monster_snd/CMakeLists.txt @@ -45,4 +45,4 @@ target_link_libraries(${PROJECT_NAME} ecal_install_sample(${PROJECT_NAME}) -set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER samples/cpp/monster) \ No newline at end of file +set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER samples/cpp/pubsub/flatbuffers) \ No newline at end of file From e78be4116873cf0a405c9ea88db46d0eab980822 Mon Sep 17 00:00:00 2001 From: DownerCase <119755054+DownerCase@users.noreply.github.com> Date: Tue, 7 May 2024 09:37:07 +0100 Subject: [PATCH 09/11] [python] Scikit-build-core driven wheel creation (#1454) --- .github/workflows/build-macos.yml | 10 -- .github/workflows/build-ubuntu-20.yml | 38 ------ .github/workflows/build-ubuntu-22.yml | 39 ------ .github/workflows/build-windows.yml | 85 +------------ .github/workflows/build_wheels.yml | 63 ++++++++++ .gitignore | 6 + CMakeLists.txt | 19 ++- CMakePresets.json | 23 ++-- app/app_pb/CMakeLists.txt | 6 - .../ecal_python_functions.cmake | 112 ----------------- contrib/ecaltime/ecaltime_pb/CMakeLists.txt | 6 - ecal/core_pb/CMakeLists.txt | 6 - lang/python/CMakeLists.txt | 68 ++++------- lang/python/core/CMakeLists.txt | 52 +++----- lang/python/ecalhdf5/CMakeLists.txt | 59 +++------ lang/python/setup.py.in | 48 -------- pyproject.toml | 115 ++++++++++++++++++ 17 files changed, 269 insertions(+), 486 deletions(-) create mode 100644 .github/workflows/build_wheels.yml delete mode 100644 cmake/helper_functions/ecal_python_functions.cmake delete mode 100644 lang/python/setup.py.in create mode 100644 pyproject.toml diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 710c093ab4..01692e5d74 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -99,10 +99,6 @@ jobs: run: cmake --build . --config Release working-directory: ${{ runner.workspace }}/_build - - name: Build Python Wheel - run: cmake --build . --target create_python_wheel --config Release - working-directory: ${{ runner.workspace }}/_build - # - name: Build Documentation C # run: cmake --build . --target documentation_c # working-directory: ${{ runner.workspace }}/_build @@ -125,9 +121,3 @@ jobs: with: name: macos-dmg path: ${{ runner.workspace }}/_build/_deploy/*.dmg - - - name: Upload Python Wheel - uses: actions/upload-artifact@v4 - with: - name: macos-python-wheel - path: ${{ runner.workspace }}/_build/_deploy/*.whl diff --git a/.github/workflows/build-ubuntu-20.yml b/.github/workflows/build-ubuntu-20.yml index 06093aa992..cfce3aec05 100644 --- a/.github/workflows/build-ubuntu-20.yml +++ b/.github/workflows/build-ubuntu-20.yml @@ -104,39 +104,6 @@ jobs: run: cmake --build . --config Release -- -k 0 working-directory: ${{ runner.workspace }}/_build - # Create Python Wheels - # The strang-looking double-cmake is an ugly workaround to force CMake to - # re-find Python, after we have changed the venv from the outside. The - # alternative would be to clean everything, which would cause an unnecessary - # rebuild of eCAL for each python Version. - - name: Build Python 3.9 Wheel - run: | - sudo apt-get -y install python3.9-dev python3.9-venv - mkdir ".venv_39" - python3.9 -m venv ".venv_39" - source ".venv_39/bin/activate" - pip install --upgrade pip - pip install wheel setuptools - cmake $GITHUB_WORKSPACE -DPython_FIND_VIRTUALENV=FIRST - cmake $GITHUB_WORKSPACE -DPython_FIND_VIRTUALENV=ONLY - cmake --build . --target create_python_wheel --config Release - shell: bash - working-directory: ${{ runner.workspace }}/_build - - - name: Build Python 3.8 Wheel - run: | - sudo apt-get -y install python3.8-dev python3.8-venv - mkdir ".venv_38" - python3.8 -m venv ".venv_38" - source ".venv_38/bin/activate" - pip install --upgrade pip - pip install wheel setuptools - cmake $GITHUB_WORKSPACE -DPython_FIND_VIRTUALENV=FIRST - cmake $GITHUB_WORKSPACE -DPython_FIND_VIRTUALENV=ONLY - cmake --build . --target create_python_wheel --config Release - shell: bash - working-directory: ${{ runner.workspace }}/_build - - name: Run Tests run: ctest -V working-directory: ${{ runner.workspace }}/_build @@ -151,8 +118,3 @@ jobs: name: ubuntu-debian path: ${{ runner.workspace }}/_build/_deploy/*.deb - - name: Upload Python Wheel - uses: actions/upload-artifact@v4 - with: - name: ubuntu-python-wheel - path: ${{ runner.workspace }}/_build/_deploy/*.whl diff --git a/.github/workflows/build-ubuntu-22.yml b/.github/workflows/build-ubuntu-22.yml index afc88c1c29..d8e0df3381 100644 --- a/.github/workflows/build-ubuntu-22.yml +++ b/.github/workflows/build-ubuntu-22.yml @@ -103,39 +103,6 @@ jobs: run: cmake --build . --config Release -- -k 0 working-directory: ${{ runner.workspace }}/_build - # Create Python Wheels - # The strang-looking double-cmake is an ugly workaround to force CMake to - # re-find Python, after we have changed the venv from the outside. The - # alternative would be to clean everything, which would cause an unnecessary - # rebuild of eCAL for each python Version. - - name: Build Python 3.11 Wheel - run: | - sudo apt-get -y install python3.11-dev python3.11-venv - mkdir ".venv_311" - python3.11 -m venv ".venv_311" - source ".venv_311/bin/activate" - pip install --upgrade pip - pip install wheel setuptools - cmake $GITHUB_WORKSPACE -DPython_FIND_VIRTUALENV=FIRST - cmake $GITHUB_WORKSPACE -DPython_FIND_VIRTUALENV=ONLY - cmake --build . --target create_python_wheel --config Release - shell: bash - working-directory: ${{ runner.workspace }}/_build - - - name: Build Python 3.10 Wheel - run: | - sudo apt-get -y install python3.10-dev python3.10-venv - mkdir ".venv_310" - python3.10 -m venv ".venv_310" - source ".venv_310/bin/activate" - pip install --upgrade pip - pip install wheel setuptools - cmake $GITHUB_WORKSPACE -DPython_FIND_VIRTUALENV=FIRST - cmake $GITHUB_WORKSPACE -DPython_FIND_VIRTUALENV=ONLY - cmake --build . --target create_python_wheel --config Release - shell: bash - working-directory: ${{ runner.workspace }}/_build - - name: Run Tests run: ctest -V working-directory: ${{ runner.workspace }}/_build @@ -149,9 +116,3 @@ jobs: with: name: ubuntu-debian path: ${{ runner.workspace }}/_build/_deploy/*.deb - - - name: Upload Python Wheel - uses: actions/upload-artifact@v4 - with: - name: ubuntu-python-wheel - path: ${{ runner.workspace }}/_build/_deploy/*.whl diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index b9d09ac053..28444c6f03 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -127,7 +127,7 @@ jobs: -DBUILD_APPS=ON ^ -DBUILD_SAMPLES=ON ^ -DBUILD_TIME=ON ^ - -DBUILD_PY_BINDING=ON ^ + -DBUILD_PY_BINDING=OFF ^ -DBUILD_CSHARP_BINDING=ON ^ -DBUILD_ECAL_TESTS=ON ^ -DECAL_INCLUDE_PY_SAMPLES=OFF ^ @@ -163,83 +163,6 @@ jobs: run: cmake --build . --config Release working-directory: ${{ runner.workspace }}/_build/complete - # Create Python. - # The strang-looking double-cmake is an ugly workaround to force CMake to - # re-find Python, after we have changed the venv from the outside. The - # alternative would be to clean everything, which would cause an unnecessary - # rebuild of eCAL and HDF5 for each python Version. - - name: Build Python 3.12 Wheel - run: | - mkdir ".venv_312" - py -3.12 -m venv ".venv_312" - CALL ".venv_312\Scripts\activate.bat" - pip install wheel setuptools - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A x64 -T v142 -DPython_FIND_VIRTUALENV=FIRST - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A x64 -T v142 -DPython_FIND_VIRTUALENV=ONLY - cmake --build . --target create_python_wheel --config Release - shell: cmd - working-directory: ${{ runner.workspace }}/_build/complete - - - name: Build Python 3.11 Wheel - run: | - mkdir ".venv_311" - py -3.11 -m venv ".venv_311" - CALL ".venv_311\Scripts\activate.bat" - pip install wheel - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A x64 -T v142 -DPython_FIND_VIRTUALENV=FIRST - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A x64 -T v142 -DPython_FIND_VIRTUALENV=ONLY - cmake --build . --target create_python_wheel --config Release - shell: cmd - working-directory: ${{ runner.workspace }}/_build/complete - - - name: Build Python 3.10 Wheel - run: | - mkdir ".venv_310" - py -3.10 -m venv ".venv_310" - CALL ".venv_310\Scripts\activate.bat" - pip install wheel - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A x64 -T v142 -DPython_FIND_VIRTUALENV=FIRST - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A x64 -T v142 -DPython_FIND_VIRTUALENV=ONLY - cmake --build . --target create_python_wheel --config Release - shell: cmd - working-directory: ${{ runner.workspace }}/_build/complete - - - name: Build Python 3.9 Wheel - run: | - mkdir ".venv_39" - py -3.9 -m venv ".venv_39" - CALL ".venv_39\Scripts\activate.bat" - pip install wheel - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A x64 -T v142 -DPython_FIND_VIRTUALENV=FIRST - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A x64 -T v142 -DPython_FIND_VIRTUALENV=ONLY - cmake --build . --target create_python_wheel --config Release - shell: cmd - working-directory: ${{ runner.workspace }}/_build/complete - - - name: Build Python 3.8 Wheel - run: | - mkdir ".venv_38" - py -3.8 -m venv ".venv_38" - CALL ".venv_38\Scripts\activate.bat" - pip install wheel - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A x64 -T v142 -DPython_FIND_VIRTUALENV=FIRST - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A x64 -T v142 -DPython_FIND_VIRTUALENV=ONLY - cmake --build . --target create_python_wheel --config Release - shell: cmd - working-directory: ${{ runner.workspace }}/_build/complete - - - name: Build Python 3.7 Wheel - run: | - mkdir ".venv_37" - py -3.7 -m venv ".venv_37" - CALL ".venv_37\Scripts\activate.bat" - pip install wheel - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A x64 -T v142 -DPython_FIND_VIRTUALENV=FIRST - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A x64 -T v142 -DPython_FIND_VIRTUALENV=ONLY - cmake --build . --target create_python_wheel --config Release - shell: cmd - working-directory: ${{ runner.workspace }}/_build/complete - # - name: Build Documentation C # run: cmake --build . --target documentation_c # working-directory: ${{ runner.workspace }}/_build @@ -266,12 +189,6 @@ jobs: name: windows-setup path: ${{ runner.workspace }}/_build/complete/_deploy/*.exe - - name: Upload Python Wheels - uses: actions/upload-artifact@v4 - with: - name: windows-python-wheels - path: ${{ runner.workspace }}/_build/complete/_deploy/*.whl - # -------------------------------------------------------------------------------------------------- sign-windows-installer: diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml new file mode 100644 index 0000000000..ec6689924f --- /dev/null +++ b/.github/workflows/build_wheels.yml @@ -0,0 +1,63 @@ +name: Build Wheels + +on: + pull_request: + branches: + - master + release: + types: [published] + workflow_dispatch: + + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + + steps: + - uses: actions/checkout@v4 + with: + submodules: 'true' + fetch-depth: 0 + + - name: Build wheels + uses: pypa/cibuildwheel@v2.17 + env: + CIBW_ARCHS: auto64 + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + build_arm64_wheels: + # Emulation takes a long time so we save it for release/manual triggering + name: Build arm64 wheels via emulation + runs-on: ubuntu-latest + if: ${{ github.event_name != 'pull_request' }} + + steps: + - uses: actions/checkout@v4 + with: + submodules: 'true' + fetch-depth: 0 + + - name: Set up QEMU + if: runner.os == 'Linux' + uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 + + - name: Build wheels + uses: pypa/cibuildwheel@v2.17 + env: + CIBW_ARCHS_LINUX: aarch64 + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-arm64 + path: ./wheelhouse/*.whl + diff --git a/.gitignore b/.gitignore index 78c44f672e..4afb4f2396 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,12 @@ Thumbs.db /_vs_out* /out +# Python building and distribution +/_python_build/ +/dist/ +/lang/python/core/ecal/_version.py +/wheelhouse/ + # Binary libraries /thirdparty/npcap/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d5ee871cd..3777ea5eef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,7 +88,6 @@ option(BUILD_APPS "Build the eCAL applications" option(BUILD_SAMPLES "Build the eCAL samples" ON) option(BUILD_TIME "Build the eCAL time interfaces" ON) option(BUILD_PY_BINDING "Build eCAL python binding" OFF) -option(BUILD_STANDALONE_PY_WHEEL "Build eCAL python binding as standalone wheel" OFF) option(BUILD_CSHARP_BINDING "Build eCAL C# binding" OFF) option(BUILD_ECAL_TESTS "Build the eCAL google tests" OFF) @@ -125,7 +124,18 @@ option(CPACK_PACK_WITH_INNOSETUP "Create Innosetup installer for t set(ECAL_INSTALL_PYTHON_DIR "bin" CACHE PATH "Location to install the Python extension modules. Might be set by setupdtools install.") -set(ECAL_BUILD_VERSION "0.0.0" CACHE STRING "Inject a build version if not building from a git repository") +if(DEFINED SKBUILD_PROJECT_VERSION) + message(STATUS + "Using version from scikit-build-core: ${SKBUILD_PROJECT_VERSION}" + ) + set(ECAL_BUILD_VERSION "${SKBUILD_PROJECT_VERSION}" CACHE STRING + "Version provided by scikit-build-core and setuptools-scm" FORCE + ) +else() + set(ECAL_BUILD_VERSION "0.0.0" CACHE STRING + "Inject a build version if not building from a git repository" + ) +endif() set(ECAL_CSHARP_BUILD_SAMPLES ${BUILD_SAMPLES}) @@ -172,10 +182,12 @@ set(eCAL_VERSION_PATCH ${GIT_REVISION_PATCH}) set(eCAL_VERSION_STRING ${eCAL_VERSION_MAJOR}.${eCAL_VERSION_MINOR}.${eCAL_VERSION_PATCH}) set(eCAL_VERSION ${eCAL_VERSION_STRING}) +message(STATUS "eCAL version: ${eCAL_VERSION_STRING}") +message(DEBUG "eCAL git describe tag: ${GIT_DESCRIBE_TAG}") + include(helper_functions/ecal_add_functions) include(helper_functions/ecal_helper_functions) include(helper_functions/ecal_install_functions) -include(helper_functions/ecal_python_functions) if(MSVC) set(eCAL_PLATFORM_TOOLSET ${CMAKE_VS_PLATFORM_TOOLSET}) @@ -465,7 +477,6 @@ message(STATUS "BUILD_APPS : ${BUILD_APPS}") message(STATUS "BUILD_SAMPLES : ${BUILD_SAMPLES}") message(STATUS "BUILD_TIME : ${BUILD_TIME}") message(STATUS "BUILD_PY_BINDING : ${BUILD_PY_BINDING}") -message(STATUS "BUILD_STANDALONE_PY_WHEEL : ${BUILD_STANDALONE_PY_WHEEL}") message(STATUS "BUILD_CSHARP_BINDING : ${BUILD_CSHARP_BINDING}") message(STATUS "BUILD_ECAL_TESTS : ${BUILD_ECAL_TESTS}") message(STATUS "ECAL_INCLUDE_PY_SAMPLES : ${ECAL_INCLUDE_PY_SAMPLES}") diff --git a/CMakePresets.json b/CMakePresets.json index 986f0f0f8f..fcf0472f7d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -62,20 +62,20 @@ } }, { - "name": "wheel", + "name": "python", "inherits": "core", - "displayName": "Python Wheel", - "description": "Minimal build for standalone python wheel", + "displayName": "Python Extensions", + "description": "Minimal build for Python extensions", "cacheVariables": { "HAS_HDF5": "ON", "BUILD_PY_BINDING": "ON", - "BUILD_STANDALONE_PY_WHEEL": "ON", - "ECAL_THIRDPARTY_BUILD_HDF5": null + "BUILD_SHARED": "OFF", + "ECAL_THIRDPARTY_BUILD_HDF5": "ON" } }, { "name": "docs", - "inherits": "wheel", + "inherits": "python", "displayName": "Documentation", "description": "Build documentation", "cacheVariables": { @@ -92,6 +92,7 @@ "HAS_CURL": "ON", "HAS_FTXUI": "ON", "BUILD_APPS": "ON", + "BUILD_SAMPLES": "ON", "ECAL_THIRDPARTY_BUILD_FINEFTP": "ON", "ECAL_THIRDPARTY_BUILD_FTXUI": "ON", "ECAL_THIRDPARTY_BUILD_SPDLOG": "ON", @@ -119,16 +120,16 @@ "configurePreset": "core" }, { - "name": "wheel", - "description": "Build standalone python wheel", - "configurePreset": "wheel", - "targets": "create_python_wheel" + "name": "python", + "description": "Build python extensions", + "configurePreset": "python", + "targets": "ecal_python" }, { "name": "docs", "description": "Build sphinx documentation", "configurePreset": "docs", - "targets": "documentation_sphinx" + "targets": "documentation_sphinx" }, { "name": "cli", diff --git a/app/app_pb/CMakeLists.txt b/app/app_pb/CMakeLists.txt index edd036188f..4118e26256 100644 --- a/app/app_pb/CMakeLists.txt +++ b/app/app_pb/CMakeLists.txt @@ -73,10 +73,4 @@ target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14) ecal_install_library(${PROJECT_NAME}) -if(BUILD_PY_BINDING) - protobuf_generate_python_ext(python_sources ${PYTHON_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/src ${ProtoFiles}) - target_sources(${PROJECT_NAME} PRIVATE ${python_sources}) - set_source_files_properties(${python_sources} PROPERTIES HEADER_FILE_ONLY TRUE) -endif() - set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER app/app_pb) diff --git a/cmake/helper_functions/ecal_python_functions.cmake b/cmake/helper_functions/ecal_python_functions.cmake deleted file mode 100644 index b51b5b179f..0000000000 --- a/cmake/helper_functions/ecal_python_functions.cmake +++ /dev/null @@ -1,112 +0,0 @@ -# ========================= eCAL LICENSE ================================= -# -# Copyright (C) 2016 - 2019 Continental Corporation -# -# 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. -# -# ========================= eCAL LICENSE ================================= - -set(PYTHON_BINARY_DIR ${CMAKE_BINARY_DIR}/python) -set(PYTHON_BINARY_MODULE_DIR ${CMAKE_BINARY_DIR}/python/ecal) - -#! ecal_add_python_module : this function adds a python module -# -# This function -# -# \arg:TARGET_NAME the first argument -# \param:SOURCES SOURCES specify the fooness of the function -# \param:PYTHON_CODE PYTHON_CODE should always be 42 -# \group:GROUP1 GROUP1 is a list of project to foo -# -function(ecal_add_python_module TARGET_NAME) - - set(multiValueArgs SOURCES) - set(singleValueArgs PYTHON_CODE) - cmake_parse_arguments(ARGUMENTS - "" - "${singleValueArgs}" - "${multiValueArgs}" ${ARGN} ) - - if(NOT ARGUMENTS_SOURCES AND NOT ARGUMENTS_PYTHON_CODE) - message(ERROR "Error in ecal_add_python_module: Please specify SOURCES and / or PYTHON_CODE arguments") - endif() - - - # if Sources are specified, a library is created - if(ARGUMENTS_SOURCES) - Python_add_library(${TARGET_NAME} MODULE ${ARGUMENTS_SOURCES}) - set_target_properties(${TARGET_NAME} - PROPERTIES - PREFIX "" - LIBRARY_OUTPUT_DIRECTORY_DEBUG "${PYTHON_BINARY_MODULE_DIR}" - LIBRARY_OUTPUT_DIRECTORY_RELEASE "${PYTHON_BINARY_MODULE_DIR}" - LIBRARY_OUTPUT_DIRECTORY "${PYTHON_BINARY_MODULE_DIR}" - DEBUG_POSTFIX "_d" - ) - # if no sources are specified, then a custom target needs to be created - else() - add_custom_target(${TARGET_NAME} ALL - COMMENT "Custom python target: ${TARGET_NAME}") - endif() - - if(ARGUMENTS_PYTHON_CODE) - # Copy all files from the source folder to the python binary directory. - get_filename_component(absolute_folder_python_files ${ARGUMENTS_PYTHON_CODE} ABSOLUTE) - file(GLOB_RECURSE relative_python_files - RELATIVE ${absolute_folder_python_files} - LIST_DIRECTORIES false - CONFIGURE_DEPENDS - ${absolute_folder_python_files}/*.py) - - foreach (f ${relative_python_files}) - set(origin_file ${absolute_folder_python_files}/${f}) - set(destination_file ${PYTHON_BINARY_MODULE_DIR}/${f}) - configure_file(${origin_file} ${destination_file} COPYONLY) - endforeach() - endif() -endfunction() - -function(ecal_add_pybind11_module TARGET_NAME) - set(multiValueArgs SOURCES) - set(singleValueArgs PYTHON_CODE) - cmake_parse_arguments(ARGUMENTS - "" - "${singleValueArgs}" - "${multiValueArgs}" ${ARGN} ) - - if(NOT ARGUMENTS_SOURCES AND NOT ARGUMENTS_PYTHON_CODE) - message(ERROR "Error in ecal_add_python_module: Please specify SOURCES and / or PYTHON_CODE arguments") - endif() - - - pybind11_add_module(${TARGET_NAME} ${ARGUMENTS_SOURCES}) - set_target_properties(${TARGET_NAME} - PROPERTIES - PREFIX "" - LIBRARY_OUTPUT_DIRECTORY_DEBUG "${PYTHON_BINARY_MODULE_DIR}" - LIBRARY_OUTPUT_DIRECTORY_RELEASE "${PYTHON_BINARY_MODULE_DIR}" - LIBRARY_OUTPUT_DIRECTORY "${PYTHON_BINARY_MODULE_DIR}" - DEBUG_POSTFIX "_d" - ) - - if(ARGUMENTS_PYTHON_CODE) - add_custom_command( - TARGET ${TARGET_NAME} - PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${ARGUMENTS_PYTHON_CODE} ${PYTHON_BINARY_MODULE_DIR} - ) - endif() -endfunction () - -function(ecal_install_python_module) -endfunction() \ No newline at end of file diff --git a/contrib/ecaltime/ecaltime_pb/CMakeLists.txt b/contrib/ecaltime/ecaltime_pb/CMakeLists.txt index 4c4dc0ab16..59998dfefe 100644 --- a/contrib/ecaltime/ecaltime_pb/CMakeLists.txt +++ b/contrib/ecaltime/ecaltime_pb/CMakeLists.txt @@ -62,10 +62,4 @@ target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14) ecal_install_library(${PROJECT_NAME}) -if(BUILD_PY_BINDING) - protobuf_generate_python_ext(python_sources ${PYTHON_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/src ${ProtoFiles}) - target_sources(${PROJECT_NAME} PRIVATE ${python_sources}) - set_source_files_properties(${python_sources} PROPERTIES HEADER_FILE_ONLY TRUE) -endif() - set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER contrib/ecaltime/ecaltime_pb) diff --git a/ecal/core_pb/CMakeLists.txt b/ecal/core_pb/CMakeLists.txt index 7f07e98b44..7295221be0 100644 --- a/ecal/core_pb/CMakeLists.txt +++ b/ecal/core_pb/CMakeLists.txt @@ -69,10 +69,4 @@ target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14) ecal_install_library(${PROJECT_NAME}) -if(BUILD_PY_BINDING) - protobuf_generate_python_ext(python_sources ${PYTHON_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/src ${ProtoFiles}) - target_sources(${PROJECT_NAME} PRIVATE ${python_sources}) - set_source_files_properties(${python_sources} PROPERTIES HEADER_FILE_ONLY TRUE) -endif() - set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER core) diff --git a/lang/python/CMakeLists.txt b/lang/python/CMakeLists.txt index be9c65a944..eeeee67a9d 100644 --- a/lang/python/CMakeLists.txt +++ b/lang/python/CMakeLists.txt @@ -5,9 +5,9 @@ # 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. @@ -16,54 +16,34 @@ # # ========================= eCAL LICENSE ================================= -project(_ecal_py) - -find_package(Python COMPONENTS Development Interpreter) -find_package(Protobuf REQUIRED) - -set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in") -set(SETUP_PY "${PYTHON_BINARY_DIR}/setup.py") - -# Create a shortened protobuf version string -string(REPLACE "." ";" Protobuf_VERSION_components "${Protobuf_VERSION}") -list(LENGTH Protobuf_VERSION_components Protobuf_VERSION_components_length) - -if ("${Protobuf_VERSION_components_length}" LESS "2") - message(FATAL_ERROR "Unable to determine protobuf version for python binding") -endif() -list(GET Protobuf_VERSION_components 0 Protobuf_VERSION_major) -list(GET Protobuf_VERSION_components 1 Protobuf_VERSION_minor) -MATH(EXPR Protobuf_Version_minor_inc "${Protobuf_VERSION_minor}+1") - -if (Protobuf_VERSION VERSION_LESS "3.19") - set(Protobuf_required_versions ">=${Protobuf_VERSION},<=3.20") -else() - set(Protobuf_required_versions ">=${Protobuf_VERSION}") -endif() +# 3.18 required for Development.Module. +# prior versions using just Development would also search for artifacts for +# embedding, which the manylinux containers don't provide +cmake_minimum_required(VERSION 3.18...3.26) +project(ecal_python) +find_package(Python REQUIRED COMPONENTS Development.Module Interpreter) -configure_file(${SETUP_PY_IN} ${SETUP_PY} @ONLY) +# Convenience target to have all Python targets buildable via one name +add_custom_target(${PROJECT_NAME}) -if(Python_Interpreter_FOUND) - message(STATUS "Python interpreter found: ${Python_EXECUTABLE}") +# We will want the shared objects to look relative to themselves for vendored +# dependencies, like eCAL core and hdf5 +# NB: Even though ${ORIGIN} and $ORIGIN are both valid, auditwheel only +# understands $ORIGIN +set(CMAKE_INSTALL_RPATH "\$ORIGIN") - # on make install the python module gets installed directly by invocing python interpreter - # TODO: this needs to be tested on windows also - message(STATUS "Installing Python extensions") - #install(CODE "execute_process(COMMAND ${Python_EXECUTABLE} setup.py install --user WORKING_DIRECTORY ${PYTHON_BINARY_DIR})") - add_custom_target(create_python_wheel - ${Python_EXECUTABLE} setup.py bdist_wheel --dist-dir=${BUILD_DEPLOY_DIRECTORY} - DEPENDS _ecal_core_py _ecal_hdf5_py - WORKING_DIRECTORY ${PYTHON_BINARY_DIR} - COMMENT "Creating python wheel" - ) - - set_property(TARGET ${create_python_wheel} PROPERTY FOLDER lang/python) -else() - message(FATAL_ERROR "Could not find python interpreter! This is needed to install the eCAL python binding!") -endif() +# Directly build with the install runtime paths as these shared objects aren't +# for build tree use. +set(CMAKE_BUILD_WITH_INSTALL_RPATH "ON") add_subdirectory(core) +add_dependencies(${PROJECT_NAME} _ecal_core_py) + if(HAS_HDF5) add_subdirectory(ecalhdf5) + add_dependencies(${PROJECT_NAME} _ecal_hdf5_py) +else() + message(WARNING "Building Python bindings without HDF5 support") endif() + diff --git a/lang/python/core/CMakeLists.txt b/lang/python/core/CMakeLists.txt index 0206b42252..c6d1972e1c 100644 --- a/lang/python/core/CMakeLists.txt +++ b/lang/python/core/CMakeLists.txt @@ -5,9 +5,9 @@ # 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. @@ -16,51 +16,29 @@ # # ========================= eCAL LICENSE ================================= -if (WIN32) - add_custom_target(copy_ecal_core_dll ALL - COMMAND cmake -E copy_if_different "$" "${PYTHON_BINARY_MODULE_DIR}" - COMMENT "Copy eCAL Core DLL to python directory" - DEPENDS eCAL::core - ) - set_property(TARGET copy_ecal_core_dll PROPERTY FOLDER lang/python/core) -endif() - -# ========================================== - project(_ecal_core_py) -find_package(Python COMPONENTS Development Interpreter) -find_package(Protobuf REQUIRED) - -set(ecal_lang_py_src - src/ecal_clang.cpp - src/ecal_clang.h - src/ecal_wrap.cxx +python_add_library(${PROJECT_NAME} MODULE WITH_SOABI + src/ecal_clang.cpp + src/ecal_clang.h + src/ecal_wrap.cxx ) -ecal_add_python_module(${PROJECT_NAME} SOURCES ${ecal_lang_py_src} PYTHON_CODE ${CMAKE_CURRENT_SOURCE_DIR}/ecal) - target_link_libraries(${PROJECT_NAME} - PRIVATE - eCAL::core + PRIVATE + eCAL::core ) target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_14) -if (WIN32) - add_dependencies(${PROJECT_NAME} copy_ecal_core_dll) -endif() - -set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER lang/python/core) +set_target_properties(${PROJECT_NAME} PROPERTIES + FOLDER lang/python/core +) -if(BUILD_STANDALONE_PY_WHEEL AND NOT WIN32) - find_package(patchelf REQUIRED) - add_custom_command(TARGET ${PROJECT_NAME} - POST_BUILD - COMMAND "${CMAKE_COMMAND}" ARGS "-DRUNTIME_FILE=\"$\"" "-DRUNTIME_DEP_FILTER=\"ecal_core|protobuf\"" "-DRUNTIME_DEPLOY_DIR=\"${PYTHON_BINARY_MODULE_DIR}\"" -P "${CMAKE_SOURCE_DIR}/cmake/deploy_runtime_deps.cmake" - COMMAND "${PATCHELF_COMMAND}" ARGS "--remove-rpath" "\"$\"" - COMMAND "${PATCHELF_COMMAND}" ARGS "--force-rpath" "--set-rpath" "\\$$ORIGIN" "\"$\"") -endif() +install(TARGETS ${PROJECT_NAME} core + RUNTIME DESTINATION ecal COMPONENT python EXCLUDE_FROM_ALL + LIBRARY DESTINATION ecal COMPONENT python EXCLUDE_FROM_ALL NAMELINK_SKIP +) if(ECAL_INCLUDE_PY_SAMPLES) if(WIN32) diff --git a/lang/python/ecalhdf5/CMakeLists.txt b/lang/python/ecalhdf5/CMakeLists.txt index 48345a8930..560b878730 100644 --- a/lang/python/ecalhdf5/CMakeLists.txt +++ b/lang/python/ecalhdf5/CMakeLists.txt @@ -5,9 +5,9 @@ # 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. @@ -16,57 +16,34 @@ # # ========================= eCAL LICENSE ================================= -if (WIN32) - if (TARGET hdf5::hdf5-shared) - add_custom_target(copy_hdf5_dll ALL - COMMAND cmake -E copy_if_different "$" "${PYTHON_BINARY_MODULE_DIR}" - COMMENT "Copy hdf5 DLL to python directory" - DEPENDS hdf5::hdf5-shared - ) - set_property(TARGET copy_hdf5_dll PROPERTY FOLDER lang/python/hdf5) - endif() -endif() - -# ========================================== - project(_ecal_hdf5_py) -find_package(Python COMPONENTS Development) - -set(ecal_lang_py_src - src/ecalhdf5_wrap.cxx +python_add_library(${PROJECT_NAME} MODULE WITH_SOABI + src/ecalhdf5_wrap.cxx ) -ecal_add_python_module(${PROJECT_NAME} SOURCES ${ecal_lang_py_src} PYTHON_CODE ${CMAKE_CURRENT_SOURCE_DIR}/ecal) - target_link_libraries(${PROJECT_NAME} - PRIVATE - eCAL::hdf5 + PRIVATE + eCAL::hdf5 ) target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_14) -if (WIN32) - if (TARGET hdf5::hdf5-shared) - add_dependencies(${PROJECT_NAME} copy_hdf5_dll) - endif() -endif() - -if(MSVC) - set_property(TARGET ${PROJECT_NAME} PROPERTY LINK_FLAGS "/ignore:4098,4099") -endif(MSVC) - -set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER lang/python/hdf5) +set_target_properties(${PROJECT_NAME} PROPERTIES + FOLDER lang/python/hdf5 +) -if(BUILD_STANDALONE_PY_WHEEL AND NOT WIN32) - find_package(patchelf REQUIRED) - add_custom_command(TARGET ${PROJECT_NAME} - POST_BUILD - COMMAND "${CMAKE_COMMAND}" ARGS "-DRUNTIME_FILE=\"$\"" "-DRUNTIME_DEP_FILTER=\"hdf5|sz|aec\"" "-DRUNTIME_DEPLOY_DIR=\"${PYTHON_BINARY_MODULE_DIR}\"" -P "${CMAKE_SOURCE_DIR}/cmake/deploy_runtime_deps.cmake" - COMMAND "${PATCHELF_COMMAND}" ARGS "--remove-rpath" "\"$\"" - COMMAND "${PATCHELF_COMMAND}" ARGS "--force-rpath" "--set-rpath" "\\$$ORIGIN" "\"$\"") +if(TARGET hdf5-shared) + install(TARGETS ${PROJECT_NAME} hdf5-shared + RUNTIME DESTINATION ecal COMPONENT python EXCLUDE_FROM_ALL + LIBRARY DESTINATION ecal COMPONENT python EXCLUDE_FROM_ALL NAMELINK_SKIP + ) endif() +install(TARGETS ${PROJECT_NAME} + DESTINATION ecal COMPONENT python EXCLUDE_FROM_ALL +) + if(ECAL_INCLUDE_PY_SAMPLES) if(WIN32) diff --git a/lang/python/setup.py.in b/lang/python/setup.py.in deleted file mode 100644 index 6a6028d645..0000000000 --- a/lang/python/setup.py.in +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python - -# ========================= eCAL LICENSE ================================= -# -# Copyright (C) 2016 - 2019 Continental Corporation -# -# 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. -# -# ========================= eCAL LICENSE ================================= - -import setuptools -import platform -from setuptools.dist import Distribution - -class BinaryDistribution(Distribution): - """Distribution which always forces a binary package with platform name""" - def has_ext_modules(self): - return True - -# this is the input file for cmake. for further details have a look at the -# CMakeLists.txt in this directory. - -setuptools.setup ( - name = 'ecal5', - version = '@eCAL_VERSION_STRING@', - description = 'This is the eCAL python API', - license = 'Apache 2.0', - author = 'Rex Schilasky', - author_email = 'rex.schilasky@continental-corporation.com', - packages = setuptools.find_packages(), - data_files = [], - package_data = {'ecal': ['*.pyd', '*.so', '*.so.*', '*.dll']}, - install_requires = [ - 'protobuf@Protobuf_required_versions@' - ], - zip_safe=False, - distclass = BinaryDistribution, -) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..fd6a1605c3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,115 @@ +[project] # Project metadata +name = "eclipse-ecal" +readme = "README.md" +requires-python = ">=3.7" +license = { "file" = "LICENSE.txt" } +authors = [ + { "name" = "Kerstin Keller", "email" = "kerstin.keller@continental.com" }, +] +keywords = ["DDS", "Middlware"] +classifiers = ["Topic :: Scientific/Engineering"] +dependencies = ["protobuf >= 3.8, == 3.*"] +# setuptools-scm will grab the version from the latest git tag +dynamic = ["version"] + +[project.urls] +"Documentation" = "https://eclipse-ecal.github.io/ecal" +"Source" = "https://github.com/eclipse-ecal/ecal" + +[build-system] # How pip and other frontends should build this project +# Version 8 of setuptools_scm drops Python 3.7 +requires = ["scikit-build-core>=0.8", "setuptools_scm~=7.1"] +build-backend = "scikit_build_core.build" + +[tool.setuptools_scm] +write_to = "lang/python/core/ecal/_version.py" + +[tool.scikit-build] +# Setuptools-scm to provide the package version from git +metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" +# Will be installed from PyPI if system version is too old/missing +cmake.version = ">=3.18" +cmake.targets = ["ecal_python"] # Targets to build +build-dir = "./_python_build" # Build directory for unisolated builds +install.components = ["python"] # CMake component to install +# Directory structure to copy as Python package, last path component is +# the package name +wheel.packages = ["lang/python/core/ecal", "lang/python/ecalhdf5/ecal"] + +# Files to include in the source archive to build from +sdist.exclude = ["*"] +sdist.include = [ + "/CMakeLists.txt", + "/pyproject.toml", + "/LICENSE.txt", + "/NOTICE.txt", + "/README.md", + "/app/app_pb/", + "/app/apps/", # TODO: Remove this directory + "/app/rec/rec_addon_*/", # TODO: Remove this directory + "/cmake/", + "/contrib/", + "/cpack/", + "/ecal/core/", + "/ecal/service/", + "/ecal/CMakeLists.txt", + "/lib/", + "/licenses/", + "lang/python/", + + "thirdparty/cmakefunctions/", + "thirdparty/protobuf/", + "thirdparty/recycle/", + "thirdparty/simpleini/", + "thirdparty/tclap/", + "thirdparty/tcp_pubsub/", + + "thirdparty/asio/", + "!thirdparty/asio/asio/asio/src/", + + # HDF5 has a lot of stuff we don't use + "/thirdparty/hdf5/", + "!/thirdparty/hdf5/hdf5/tools/", + "!/thirdparty/hdf5/hdf5/**/test*/", + "!/thirdparty/hdf5/hdf5/**/examples*/", + "!/thirdparty/hdf5/hdf5/hl/", + "!/thirdparty/hdf5/hdf5/doxygen/", + "!/thirdparty/hdf5/hdf5/java/", + "!/thirdparty/hdf5/hdf5/fortran/", + +] + +[tool.scikit-build.cmake.define] +CMAKE_PROJECT_TOP_LEVEL_INCLUDES = "cmake/submodule_dependencies.cmake" +HAS_HDF5 = "ON" +HAS_QT = "OFF" +HAS_CURL = "OFF" +HAS_FTXUI = "OFF" +BUILD_APPS = "OFF" +BUILD_SAMPLES = "OFF" +BUILD_TIME = "OFF" +BUILD_PY_BINDING = "ON" +BUILD_SHARED_LIBS = "OFF" +ECAL_INSTALL_SAMPLE_SOURCES = "OFF" +ECAL_THIRDPARTY_BUILD_CMAKE_FUNCTIONS = "ON" +ECAL_THIRDPARTY_BUILD_TCP_PUBSUB = "ON" +ECAL_THIRDPARTY_BUILD_RECYCLE = "ON" +ECAL_THIRDPARTY_BUILD_PROTOBUF = "ON" +ECAL_THIRDPARTY_BUILD_FINEFTP = "OFF" +ECAL_THIRDPARTY_BUILD_FTXUI = "OFF" +ECAL_THIRDPARTY_BUILD_SPDLOG = "OFF" +ECAL_THIRDPARTY_BUILD_TERMCOLOR = "OFF" +ECAL_THIRDPARTY_BUILD_TINYXML2 = "OFF" +ECAL_THIRDPARTY_BUILD_YAML-CPP = "OFF" +ECAL_THIRDPARTY_BUILD_CURL = "OFF" +ECAL_THIRDPARTY_BUILD_HDF5 = "ON" +# Stop HDF5 trying to export eCALCoreTargets due to the hack +# with HDF5_EXPORTED_TARGETS +HDF5_EXTERNALLY_CONFIGURED = "ON" + +[tool.cibuildwheel] +# Only support 64-bit builds for now as CMake gets confused when the +# architecture changes and causes link errors with Python +archs = ["auto64"] +# eCAL has build errors on musl libc +skip = [ "*-musllinux*" ] From 650d9694deb66c37e4605e0925ef8b20756196f3 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Tue, 7 May 2024 13:42:28 +0200 Subject: [PATCH 10/11] [GH Actions] Cleaned Windows and Ubuntu Actions and updated capnproto (#1571) - Combined Ubuntu builds in a single matrix workflow - Slightly increased performance of Ubuntu builds by de-initializing unneeded submodules - Updated capnproto in Ubuntu workflow - Renamed Ubuntu and Windows artifacts to proper names --- .github/workflows/build-ubuntu-20.yml | 120 ---------------- .github/workflows/build-ubuntu-22.yml | 118 ---------------- .github/workflows/build-ubuntu.yml | 188 ++++++++++++++++++++++++++ .github/workflows/build-windows.yml | 62 +++++---- README.md | 2 +- 5 files changed, 225 insertions(+), 265 deletions(-) delete mode 100644 .github/workflows/build-ubuntu-20.yml delete mode 100644 .github/workflows/build-ubuntu-22.yml create mode 100644 .github/workflows/build-ubuntu.yml diff --git a/.github/workflows/build-ubuntu-20.yml b/.github/workflows/build-ubuntu-20.yml deleted file mode 100644 index cfce3aec05..0000000000 --- a/.github/workflows/build-ubuntu-20.yml +++ /dev/null @@ -1,120 +0,0 @@ -name: Build Ubuntu 20.04 - -on: - push: - pull_request: - branches: - - master - -jobs: - build-ubuntu: - runs-on: ubuntu-20.04 - - env: - # enable starting Qt GUI Applications - QT_QPA_PLATFORM: offscreen - - steps: - - name: Install Dependencies - run: | - sudo apt update - sudo apt-get install ninja-build doxygen graphviz libcurl4-openssl-dev libprotobuf-dev libprotoc-dev protobuf-compiler libhdf5-dev qt5-default libqt5opengl5-dev libqt5svg5-dev libyaml-cpp-dev - - - name: Install Cap'n Proto - run: | - mkdir "${{ runner.workspace }}/capnp" - cd "${{ runner.workspace }}/capnp" - curl -O https://capnproto.org/capnproto-c++-0.9.0.tar.gz - tar zxf capnproto-c++-0.9.0.tar.gz - cd capnproto-c++-0.9.0 - ./configure - make -j - sudo make install - - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: 'true' - fetch-depth: 0 - - - name: Install Python requirements - shell: bash - run: | - sudo apt-get -y install python3.9-dev python3.9-venv - mkdir ".venv_build" - python3.9 -m venv ".venv_build" - source ".venv_build/bin/activate" - pip install --upgrade pip - pip install wheel setuptools - pip install -r "$GITHUB_WORKSPACE/doc/requirements.txt" - - - name: CMake - run: | - source ".venv_build/bin/activate" - - export CC=/usr/bin/gcc-9 - export CXX=/usr/bin/g++-9 - mkdir "${{ runner.workspace }}/_build" - cd "${{ runner.workspace }}/_build" - - cmake $GITHUB_WORKSPACE -G "Ninja" \ - -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=cmake/submodule_dependencies.cmake \ - -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - -DHAS_HDF5=ON \ - -DHAS_QT=ON \ - -DHAS_CURL=ON \ - -DHAS_CAPNPROTO=ON \ - -DHAS_FTXUI=ON \ - -DBUILD_DOCS=ON \ - -DBUILD_APPS=ON \ - -DBUILD_SAMPLES=ON \ - -DBUILD_TIME=ON \ - -DBUILD_PY_BINDING=ON \ - -DBUILD_STANDALONE_PY_WHEEL=ON \ - -DBUILD_CSHARP_BINDING=OFF \ - -DBUILD_ECAL_TESTS=ON \ - -DECAL_INCLUDE_PY_SAMPLES=OFF \ - -DECAL_INSTALL_SAMPLE_SOURCES=ON \ - -DECAL_JOIN_MULTICAST_TWICE=OFF \ - -DECAL_NPCAP_SUPPORT=OFF \ - -DECAL_THIRDPARTY_BUILD_CMAKE_FUNCTIONS=ON \ - -DECAL_THIRDPARTY_BUILD_PROTOBUF=OFF \ - -DECAL_THIRDPARTY_BUILD_SPDLOG=ON \ - -DECAL_THIRDPARTY_BUILD_TINYXML2=ON \ - -DECAL_THIRDPARTY_BUILD_FINEFTP=ON \ - -DECAL_THIRDPARTY_BUILD_CURL=OFF \ - -DECAL_THIRDPARTY_BUILD_GTEST=ON \ - -DECAL_THIRDPARTY_BUILD_HDF5=OFF \ - -DECAL_THIRDPARTY_BUILD_RECYCLE=ON \ - -DECAL_THIRDPARTY_BUILD_TCP_PUBSUB=ON \ - -DECAL_THIRDPARTY_BUILD_QWT=ON \ - -DECAL_THIRDPARTY_BUILD_YAML-CPP=OFF \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_SYSCONFDIR=/etc \ - -DCMAKE_INSTALL_PREFIX=/usr \ - -DCMAKE_INSTALL_LOCALSTATEDIR=/var \ - -DCMAKE_INSTALL_LIBDIR=lib/x86_64-linux-gnu \ - -DPython_FIND_VIRTUALENV=ONLY - - sudo mkdir /etc/ecal - sudo cp "$GITHUB_WORKSPACE/ecal/core/cfg/ecal.ini" /etc/ecal - shell: bash - - - name: Build Release - run: cmake --build . --config Release -- -k 0 - working-directory: ${{ runner.workspace }}/_build - - - name: Run Tests - run: ctest -V - working-directory: ${{ runner.workspace }}/_build - - - name: Pack - run: cpack -G DEB - working-directory: ${{ runner.workspace }}/_build - - - name: Upload Debian - uses: actions/upload-artifact@v4 - with: - name: ubuntu-debian - path: ${{ runner.workspace }}/_build/_deploy/*.deb - diff --git a/.github/workflows/build-ubuntu-22.yml b/.github/workflows/build-ubuntu-22.yml deleted file mode 100644 index d8e0df3381..0000000000 --- a/.github/workflows/build-ubuntu-22.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: Build Ubuntu 22.04 - -on: - push: - pull_request: - branches: - - master - -jobs: - build-ubuntu: - runs-on: ubuntu-22.04 - - env: - # enable starting Qt GUI Applications - QT_QPA_PLATFORM: offscreen - - steps: - - name: Install Dependencies - run: | - sudo apt update - sudo apt-get install ninja-build doxygen graphviz libcurl4-openssl-dev libprotobuf-dev libprotoc-dev protobuf-compiler libhdf5-dev qtbase5-dev libqt5opengl5-dev libqt5svg5-dev libyaml-cpp-dev - - - name: Install Cap'n Proto - run: | - mkdir "${{ runner.workspace }}/capnp" - cd "${{ runner.workspace }}/capnp" - curl -O https://capnproto.org/capnproto-c++-0.9.0.tar.gz - tar zxf capnproto-c++-0.9.0.tar.gz - cd capnproto-c++-0.9.0 - ./configure - make -j - sudo make install - - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: 'true' - fetch-depth: 0 - - - name: Install Python requirements - shell: bash - run: | - sudo apt-get -y install python3-dev python3-venv - mkdir ".venv_build" - python3 -m venv ".venv_build" - source ".venv_build/bin/activate" - pip install --upgrade pip - pip install wheel setuptools - pip install -r "$GITHUB_WORKSPACE/doc/requirements.txt" - - - name: CMake - run: | - source ".venv_build/bin/activate" - - export CC=/usr/bin/gcc-11 - export CXX=/usr/bin/g++-11 - mkdir "${{ runner.workspace }}/_build" - cd "${{ runner.workspace }}/_build" - cmake $GITHUB_WORKSPACE -G "Ninja" \ - -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=cmake/submodule_dependencies.cmake \ - -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - -DHAS_HDF5=ON \ - -DHAS_QT=ON \ - -DHAS_CURL=ON \ - -DHAS_CAPNPROTO=ON \ - -DHAS_FTXUI=ON \ - -DBUILD_DOCS=ON \ - -DBUILD_APPS=ON \ - -DBUILD_SAMPLES=ON \ - -DBUILD_TIME=ON \ - -DBUILD_PY_BINDING=ON \ - -DBUILD_STANDALONE_PY_WHEEL=ON \ - -DBUILD_CSHARP_BINDING=OFF \ - -DBUILD_ECAL_TESTS=ON \ - -DECAL_INCLUDE_PY_SAMPLES=OFF \ - -DECAL_INSTALL_SAMPLE_SOURCES=ON \ - -DECAL_JOIN_MULTICAST_TWICE=OFF \ - -DECAL_NPCAP_SUPPORT=OFF \ - -DECAL_THIRDPARTY_BUILD_CMAKE_FUNCTIONS=ON \ - -DECAL_THIRDPARTY_BUILD_PROTOBUF=OFF \ - -DECAL_THIRDPARTY_BUILD_SPDLOG=ON \ - -DECAL_THIRDPARTY_BUILD_TINYXML2=ON \ - -DECAL_THIRDPARTY_BUILD_FINEFTP=ON \ - -DECAL_THIRDPARTY_BUILD_CURL=OFF \ - -DECAL_THIRDPARTY_BUILD_GTEST=ON \ - -DECAL_THIRDPARTY_BUILD_HDF5=OFF \ - -DECAL_THIRDPARTY_BUILD_RECYCLE=ON \ - -DECAL_THIRDPARTY_BUILD_TCP_PUBSUB=ON \ - -DECAL_THIRDPARTY_BUILD_QWT=ON \ - -DECAL_THIRDPARTY_BUILD_YAML-CPP=OFF \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_SYSCONFDIR=/etc \ - -DCMAKE_INSTALL_PREFIX=/usr \ - -DCMAKE_INSTALL_LOCALSTATEDIR=/var \ - -DCMAKE_INSTALL_LIBDIR=lib/x86_64-linux-gnu \ - -DPython_FIND_VIRTUALENV=ONLY - - sudo mkdir /etc/ecal - sudo cp "$GITHUB_WORKSPACE/ecal/core/cfg/ecal.ini" /etc/ecal - shell: bash - - - name: Build Release - run: cmake --build . --config Release -- -k 0 - working-directory: ${{ runner.workspace }}/_build - - - name: Run Tests - run: ctest -V - working-directory: ${{ runner.workspace }}/_build - - - name: Pack - run: cpack -G DEB - working-directory: ${{ runner.workspace }}/_build - - - name: Upload Debian - uses: actions/upload-artifact@v4 - with: - name: ubuntu-debian - path: ${{ runner.workspace }}/_build/_deploy/*.deb diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml new file mode 100644 index 0000000000..c24d9f3e3e --- /dev/null +++ b/.github/workflows/build-ubuntu.yml @@ -0,0 +1,188 @@ +name: Build Ubuntu + +on: + push: + pull_request: + +jobs: + build-ubuntu: + + strategy: + fail-fast: false + matrix: + os: [ubuntu-22.04, ubuntu-20.04] + + runs-on: ${{ matrix.os }} + + env: + # enable starting Qt GUI Applications + QT_QPA_PLATFORM: offscreen + PROJECT_NAME: ecal + + steps: + + - name: Install Dependencies + run: | + sudo apt update + + if [ "${{ matrix.os }}" == "ubuntu-24.04" ]; then + sudo apt-get install ninja-build doxygen graphviz libcurl4-openssl-dev libprotobuf-dev libprotoc-dev protobuf-compiler libhdf5-dev libyaml-cpp-dev + sudo apt-get install qt6-base-dev qt6-svg-dev + sudo apt-get install libgtest-dev + sudo apt-get install python3 python3-venv python3-dev + elif [ "${{ matrix.os }}" == "ubuntu-22.04" ]; then + sudo apt-get install ninja-build doxygen graphviz libcurl4-openssl-dev libprotobuf-dev libprotoc-dev protobuf-compiler libhdf5-dev libyaml-cpp-dev + sudo apt-get install qtbase5-dev libqt5opengl5-dev libqt5svg5-dev + sudo apt-get install libgtest-dev + sudo apt-get install python3 python3-venv python3-dev + elif [ "${{ matrix.os }}" == "ubuntu-20.04" ]; then + sudo apt-get install ninja-build doxygen graphviz libcurl4-openssl-dev libprotobuf-dev libprotoc-dev protobuf-compiler libhdf5-dev libyaml-cpp-dev + sudo apt-get install qt5-default libqt5opengl5-dev libqt5svg5-dev + sudo apt-get install libgtest-dev + sudo apt-get install python3.9 python3.9-venv python3.9-dev + fi + + - name: Set variables + run: | + if [ "${{ matrix.os }}" == "ubuntu-24.04" ]; then + echo "ubuntu_codename=noble" >> "$GITHUB_ENV" + echo "python_version=3" >> "$GITHUB_ENV" # => default python 3 version + elif [ "${{ matrix.os }}" == "ubuntu-22.04" ]; then + echo "ubuntu_codename=focal" >> "$GITHUB_ENV" + echo "python_version=3" >> "$GITHUB_ENV" # => default python 3 version + elif [ "${{ matrix.os }}" == "ubuntu-20.04" ]; then + echo "ubuntu_codename=jammy" >> "$GITHUB_ENV" + echo "python_version=3.9" >> "$GITHUB_ENV" + fi + + # Get cpu architecture + echo "cpu_architecture=$(dpkg --print-architecture)" >> "$GITHUB_ENV" + + - name: Install Cap'n Proto + run: | + mkdir "${{ runner.workspace }}/capnp" + cd "${{ runner.workspace }}/capnp" + curl -O https://capnproto.org/capnproto-c++-1.0.2.tar.gz + tar zxf capnproto-c++-1.0.2.tar.gz + cd capnproto-c++-1.0.2 + ./configure + make -j$(($(nproc)*2)) + sudo make install + + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: 'false' + fetch-depth: 0 + + - name: Update / download Submodules (selected ones) + run: | + cd $GITHUB_WORKSPACE + git submodule init + git submodule deinit thirdparty/curl/curl + git submodule deinit thirdparty/gtest/googletest + git submodule deinit thirdparty/hdf5/hdf5 + git submodule deinit thirdparty/libssh2/libssh2 + git submodule deinit thirdparty/protobuf/protobuf + git submodule deinit thirdparty/zlib/zlib + git submodule update + + - name: Create venv for building docs + shell: bash + run: | + mkdir ".venv_build" + python${{ env.python_version }} -m venv ".venv_build" + source ".venv_build/bin/activate" + pip install --upgrade pip + pip install wheel setuptools + pip install -r "$GITHUB_WORKSPACE/doc/requirements.txt" + + - name: CMake + run: | + source ".venv_build/bin/activate" + + mkdir "${{ runner.workspace }}/_build" + cd "${{ runner.workspace }}/_build" + + cmake $GITHUB_WORKSPACE -G "Ninja" \ + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=cmake/submodule_dependencies.cmake \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -DHAS_HDF5=ON \ + -DHAS_QT=ON \ + -DHAS_CURL=ON \ + -DHAS_CAPNPROTO=ON \ + -DHAS_FTXUI=ON \ + -DBUILD_DOCS=ON \ + -DBUILD_APPS=ON \ + -DBUILD_SAMPLES=ON \ + -DBUILD_TIME=ON \ + -DBUILD_PY_BINDING=ON \ + -DBUILD_STANDALONE_PY_WHEEL=OFF \ + -DBUILD_CSHARP_BINDING=OFF \ + -DBUILD_ECAL_TESTS=ON \ + -DECAL_INCLUDE_PY_SAMPLES=OFF \ + -DECAL_INSTALL_SAMPLE_SOURCES=ON \ + -DECAL_JOIN_MULTICAST_TWICE=OFF \ + -DECAL_NPCAP_SUPPORT=OFF \ + -DECAL_THIRDPARTY_BUILD_CMAKE_FUNCTIONS=ON \ + -DECAL_THIRDPARTY_BUILD_PROTOBUF=OFF \ + -DECAL_THIRDPARTY_BUILD_SPDLOG=ON \ + -DECAL_THIRDPARTY_BUILD_TINYXML2=ON \ + -DECAL_THIRDPARTY_BUILD_FINEFTP=ON \ + -DECAL_THIRDPARTY_BUILD_CURL=OFF \ + -DECAL_THIRDPARTY_BUILD_GTEST=OFF \ + -DECAL_THIRDPARTY_BUILD_HDF5=OFF \ + -DECAL_THIRDPARTY_BUILD_RECYCLE=ON \ + -DECAL_THIRDPARTY_BUILD_TCP_PUBSUB=ON \ + -DECAL_THIRDPARTY_BUILD_QWT=ON \ + -DECAL_THIRDPARTY_BUILD_YAML-CPP=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_SYSCONFDIR=/etc \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_INSTALL_LOCALSTATEDIR=/var \ + -DCMAKE_INSTALL_LIBDIR=lib/x86_64-linux-gnu \ + -DPython_FIND_VIRTUALENV=ONLY + + sudo mkdir /etc/ecal + sudo cp "$GITHUB_WORKSPACE/ecal/core/cfg/ecal.ini" /etc/ecal + shell: bash + + - name: Build Release + run: cmake --build . --parallel -- -k 0 + working-directory: ${{ runner.workspace }}/_build + + - name: Run Tests + run: ctest -V + working-directory: ${{ runner.workspace }}/_build + + - name: Get Project version from git tag + shell: bash + run: | + # Use git describe to get the tag / commit description + VERSION="$(git describe --tags --dirty)" + + # Remove the leading 'v' from the tag + VERSION="${VERSION#v}" + + echo "PROJECT_VERSION=$VERSION" >> "$GITHUB_ENV" + + - name: Set output binary name + shell: bash + run: | + echo "BINARY_NAME=${{ env.PROJECT_NAME }}_${{ env.PROJECT_VERSION }}-${{ env.ubuntu_codename }}_${{ env.cpu_architecture }}" >> "$GITHUB_ENV" + + - name: CPack + run: cpack -G DEB + working-directory: ${{ runner.workspace }}/_build + + - name: Rename .deb installer + run: | + mv *.deb '${{ env.BINARY_NAME }}.deb' + shell: bash + working-directory: ${{runner.workspace}}/_build/_deploy/ + + - name: Upload Debian + uses: actions/upload-artifact@v4 + with: + name: '${{ env.BINARY_NAME }}' + path: ${{ runner.workspace }}/_build/_deploy/*.deb diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 28444c6f03..e0c631d4ae 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -1,4 +1,4 @@ -name: Build Windows Server 2019 +name: Build Windows on: push: @@ -32,20 +32,6 @@ jobs: - name: Uninstall Chocolatey run: move "$env:PROGRAMDATA\chocolatey" "$env:PROGRAMDATA\_chocolatey" -# - name: Install Cap’n Proto -# run: | -# mkdir "${{ runner.workspace }}/capnp" -# cd "${{ runner.workspace }}/capnp" -# git clone https://github.com/sandstorm-io/capnproto.git -# cd capnproto -# git checkout release-0.9.0 -# cd c++ -# mkdir _build -# cd _build -# cmake .. -G "Visual Studio 16 2019" -A x64 -# cmake --build . --parallel --config Release -# cmake --build . --target install --config Release - - name: Checkout uses: actions/checkout@v4 with: @@ -163,14 +149,6 @@ jobs: run: cmake --build . --config Release working-directory: ${{ runner.workspace }}/_build/complete -# - name: Build Documentation C -# run: cmake --build . --target documentation_c -# working-directory: ${{ runner.workspace }}/_build -# -# - name: Build Documentation C++ -# run: cmake --build . --target documentation_cpp -# working-directory: ${{ runner.workspace }}/_build - - name: Run Tests run: ctest -C Release -V working-directory: ${{ runner.workspace }}/_build/complete @@ -183,10 +161,35 @@ jobs: run: cpack -C Release working-directory: ${{ runner.workspace }}/_build/complete + - name: Get Project version from git tag + shell: powershell + run: | + # Use git describe to get the tag / commit description + $VERSION = git describe --tags --dirty + + #remove the leading 'v' from the tag, if it exists + $VERSION = $VERSION -replace '^v', '' + + echo "VERSION=$VERSION" >> "$Env:GITHUB_ENV" + + - name: Set output binary name + shell: powershell + run: | + $BINARY_NAME = "ecal_${{ env.VERSION }}-win64" + echo "BINARY_NAME=$BINARY_NAME" >> "$Env:GITHUB_ENV" + + - name: Rename the setup exe + shell: powershell + run: | + $LS_OUT = ls *.exe + $SETUP_NAME = "$($LS_OUT.Name)" + Rename-Item -Path "$SETUP_NAME" -NewName "${{ env.BINARY_NAME }}.exe" + working-directory: ${{ runner.workspace }}/_build/complete/_deploy/ + - name: Upload Windows setup uses: actions/upload-artifact@v4 with: - name: windows-setup + name: unsigned-setup path: ${{ runner.workspace }}/_build/complete/_deploy/*.exe # -------------------------------------------------------------------------------------------------- @@ -201,7 +204,7 @@ jobs: - name: Download Windows setup uses: actions/download-artifact@v4 with: - name: windows-setup + name: unsigned-setup path: ${{ runner.workspace }}/_build/complete/_deploy - name: Sign the installer on Eclipse CI @@ -371,9 +374,16 @@ jobs: JENKINS_JOB_TOKEN: ${{ secrets.JENKINS_TOKEN_GH_FILE }} working-directory: ${{ runner.workspace }}/_build/complete/_deploy + - name: Determine name of the installer for zip file + shell: powershell + run: | + $INSTALLER_NAME = "${{ env.ASSET_NAME }}" + $INSTALLER_NAME_WITHOUT_EXE = $INSTALLER_NAME.Substring(0, $INSTALLER_NAME.Length - 4) + echo "ARTIFACT_NAME=$INSTALLER_NAME_WITHOUT_EXE" >> "$Env:GITHUB_ENV" + - name: Upload Windows setup signed on Eclipse CI if: env.IS_DOWNLOAD_AVAILABLE == 'true' uses: actions/upload-artifact@v4 with: - name: windows-setup-signed + name: ${{ env.ARTIFACT_NAME }} path: ${{ runner.workspace }}/_build/complete/_deploy/${{ env.ASSET_NAME }} diff --git a/README.md b/README.md index 888904f0ad..9b6dd17567 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # eCAL - enhanced Communication Abstraction Layer -[![Build Windows Server 2019](https://github.com/eclipse-ecal/ecal/workflows/Build%20Windows%20Server%202019/badge.svg)](https://github.com/eclipse-ecal/ecal/actions?workflow=Build+Windows+Server+2019) [![Build Ubuntu 20.04](https://github.com/eclipse-ecal/ecal/workflows/Build%20Ubuntu%2020.04/badge.svg)](https://github.com/eclipse-ecal/ecal/actions?workflow=Build+Ubuntu+20.04) [![Build Ubuntu 22.04](https://github.com/eclipse-ecal/ecal/actions/workflows/build-ubuntu-22.yml/badge.svg)](https://github.com/eclipse-ecal/ecal/actions/workflows/build-ubuntu-22.yml) [![Build macOS](https://github.com/eclipse-ecal/ecal/actions/workflows/build-macos.yml/badge.svg)](https://github.com/eclipse-ecal/ecal/actions/workflows/build-macos.yml) +[![Windows](https://github.com/eclipse-ecal/ecal/actions/workflows/build-windows.yml/badge.svg)](https://github.com/eclipse-ecal/ecal/actions/workflows/build-windows.yml) [![Ubuntu](https://github.com/eclipse-ecal/ecal/actions/workflows/build-ubuntu.yml/badge.svg)](https://github.com/eclipse-ecal/ecal/actions/workflows/build-ubuntu.yml) [![macOS](https://github.com/eclipse-ecal/ecal/actions/workflows/build-macos.yml/badge.svg)](https://github.com/eclipse-ecal/ecal/actions/workflows/build-macos.yml) [![License](https://img.shields.io/github/license/continental/ecal.svg?style=flat)](LICENSE.txt) From 1fd8cde19d3d51001998cff89632d2c53e4a79b8 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Tue, 7 May 2024 15:16:59 +0200 Subject: [PATCH 11/11] [Core Service] Added support for .local mDNS in eCAL Services (#1586) Added support for resolving Hostname.local (-> mDNS) hosts - eCAL Services Lib now supports a list of endpoints to connect to. They will be tried one after another - eCAL Core uses a list of {Hostname, Hostname.local} to make the service client connect to the mDNS TLD host, if the Hostname alone was not sufficient --- .../src/service/ecal_service_client_impl.cpp | 7 +- ecal/service/CMakeLists.txt | 9 +- .../include/ecal/service/client_manager.h | 20 +- .../include/ecal/service/client_session.h | 89 +++++---- .../include/ecal/service/server_manager.h | 8 +- .../ecal_service/src/client_manager.cpp | 19 +- .../ecal_service/src/client_session.cpp | 81 ++++---- .../src/client_session_impl_base.h | 6 +- .../src/client_session_impl_v0.cpp | 151 +++++++++++---- .../ecal_service/src/client_session_impl_v0.h | 45 +++-- .../src/client_session_impl_v1.cpp | 155 +++++++++++---- .../ecal_service/src/client_session_impl_v1.h | 45 +++-- ecal/service/ecal_service/src/log_defs.h | 2 - ecal/service/ecal_service/src/protocol_v0.cpp | 9 + ecal/service/ecal_service/src/protocol_v1.cpp | 8 + ecal/service/ecal_service/src/server.cpp | 7 +- ecal/service/ecal_service/src/server_impl.cpp | 17 +- ecal/service/ecal_service/src/server_impl.h | 3 - .../ecal_service/src/server_manager.cpp | 8 +- .../src/server_session_impl_base.h | 8 +- .../src/server_session_impl_v0.cpp | 21 +- .../ecal_service/src/server_session_impl_v0.h | 8 +- .../src/server_session_impl_v1.cpp | 22 ++- .../ecal_service/src/server_session_impl_v1.h | 9 +- .../sample_client}/CMakeLists.txt | 2 +- .../samples/sample_client/src/main.cpp | 160 +++++++++++++++ .../samples/sample_server/CMakeLists.txt | 39 ++++ .../samples/sample_server/src/main.cpp | 124 ++++++++++++ .../samples/sample_standalone/CMakeLists.txt | 39 ++++ .../sample_standalone}/src/main.cpp | 12 +- .../test/src/ecal_tcp_service_test.cpp | 183 +++++++++++++++--- 31 files changed, 1044 insertions(+), 272 deletions(-) rename ecal/service/{sample => samples/sample_client}/CMakeLists.txt (97%) create mode 100644 ecal/service/samples/sample_client/src/main.cpp create mode 100644 ecal/service/samples/sample_server/CMakeLists.txt create mode 100644 ecal/service/samples/sample_server/src/main.cpp create mode 100644 ecal/service/samples/sample_standalone/CMakeLists.txt rename ecal/service/{sample => samples/sample_standalone}/src/main.cpp (91%) diff --git a/ecal/core/src/service/ecal_service_client_impl.cpp b/ecal/core/src/service/ecal_service_client_impl.cpp index 1124ee0f72..3a3bd20e10 100644 --- a/ecal/core/src/service/ecal_service_client_impl.cpp +++ b/ecal/core/src/service/ecal_service_client_impl.cpp @@ -751,7 +751,12 @@ namespace eCAL const auto port_to_use = (protocol_version == 0 ? iter.tcp_port_v0 : iter.tcp_port_v1); // Create the client and add it to the map - const auto new_client_session = client_manager->create_client(static_cast(protocol_version), iter.hname, port_to_use, event_callback); + const std::vector> endpoint_list + { + {iter.hname, port_to_use}, + {iter.hname + ".local", port_to_use}, // TODO: Make this configurable from the ecal.ini + }; + const auto new_client_session = client_manager->create_client(static_cast(protocol_version), endpoint_list, event_callback); if (new_client_session) m_client_map[iter.key] = new_client_session; } diff --git a/ecal/service/CMakeLists.txt b/ecal/service/CMakeLists.txt index 83a34bb9c0..8743239f47 100644 --- a/ecal/service/CMakeLists.txt +++ b/ecal/service/CMakeLists.txt @@ -16,15 +16,20 @@ # # ========================= eCAL LICENSE ================================= +cmake_minimum_required(VERSION 3.16) +project(ecal_service) + # Main library add_subdirectory(ecal_service) # Samples if(ECAL_CORE_BUILD_SAMPLES) - add_subdirectory(sample) + add_subdirectory(samples/sample_client) + add_subdirectory(samples/sample_server) + add_subdirectory(samples/sample_standalone) endif() # Tests if(ECAL_CORE_BUILD_TESTS) add_subdirectory(test) -endif() \ No newline at end of file +endif() diff --git a/ecal/service/ecal_service/include/ecal/service/client_manager.h b/ecal/service/ecal_service/include/ecal/service/client_manager.h index 39170c3bc1..0bb903c947 100644 --- a/ecal/service/ecal_service/include/ecal/service/client_manager.h +++ b/ecal/service/ecal_service/include/ecal/service/client_manager.h @@ -23,10 +23,16 @@ #include #include #include - -#include #include #include +#include +#include + +#include + +#include + +#include // IWYU pragma: export namespace eCAL { @@ -141,16 +147,14 @@ namespace eCAL * stopped from this central place. * * @param protocol_version The protocol version to use for the client session. If 0, the legacy buggy protocol will be used. - * @param address The address of the server to connect to - * @param port The port of the server to connect to + * @param server_list A list of endpoints to connect to. Must not be empty. The endpoints will be tried in the given order until a working endpoint is found. * @param event_callback The callback, that will be called, when the client has connected to the server or disconnected from it. The callback will be executed in the io_context thread. * * @return A shared_ptr to the newly created ClientSession instance */ - std::shared_ptr create_client(std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const ClientSession::EventCallbackT& event_callback); + std::shared_ptr create_client(std::uint8_t protocol_version + , const std::vector>& server_list + , const ClientSession::EventCallbackT& event_callback); /** * @brief Returns the number of managed client sessions diff --git a/ecal/service/ecal_service/include/ecal/service/client_session.h b/ecal/service/ecal_service/include/ecal/service/client_session.h index c7edeef3a0..6b9b7f52de 100644 --- a/ecal/service/ecal_service/include/ecal/service/client_session.h +++ b/ecal/service/ecal_service/include/ecal/service/client_session.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include #ifdef _MSC_VER #pragma warning(push) @@ -34,6 +36,7 @@ #endif #include +#include #include #include @@ -133,43 +136,38 @@ namespace eCAL * * @param io_context The io_context to use for the session and all callbacks. * @param protocol_version The protocol version to use for the session. When this is 0, the legacy buggy protocol is used. - * @param address The address of the server to connect to. May be an IP or a Hostname, IPv6 is supported. - * @param port The port of the server to connect to. + * @param server_list A list of endpoints to connect to. Must not be empty. The endpoints will be tried in the given order until a working endpoint is found. * @param event_callback The callback to be called when the session's state changes, i.e. when the session successfully connected to a server or disconnected from it. * @param logger The logger to use for logging. * @param delete_callback The callback to be called when the session is deleted. This is useful for the eCAL::service::ClientManager to keep track of the number of active sessions. * * @return The new ClientSession instance as a shared_ptr. */ - static std::shared_ptr create(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger - , const DeleteCallbackT& delete_callback); + static std::shared_ptr create(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger + , const DeleteCallbackT& delete_callback); - static std::shared_ptr create(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger = default_logger("Service Client")); + static std::shared_ptr create(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger = default_logger("Service Client")); - static std::shared_ptr create(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const DeleteCallbackT& delete_callback); + static std::shared_ptr create(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const DeleteCallbackT& delete_callback); protected: - ClientSession(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger); + ClientSession(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger); public: // Delete copy constructor and assignment operator @@ -227,28 +225,41 @@ namespace eCAL eCAL::service::Error call_service(const std::shared_ptr& request, std::shared_ptr& response); /** - * @brief Get the address that this client session has been created with. + * @brief Get the host that this client is connected to. * - * This function returns the address that this client session has been - * created with. It will not return the address of the server that this - * client session is connected to, which would actually be the same - * address, but probably resolved to an IP. + * Get the host that this client is connected to. + * If the client is not connected, this function will return an empty + * string. Otherwise, it will return the hostname from the list + * server_list that the client is connected to. * - * @return The address that this client session has been created with. + * The host is not resolved to an IP address. Use get_remote_endpoint() + * to get the actual IP address. + * + * @return The host that this client is connected to. */ - std::string get_address() const; + std::string get_host() const; /** - * @brief Get the port that this client session has been created with. + * @brief Get the port that this client session is connected to. * - * This function returns the port that this client session has been - * created with. It is not said, that the connection has been established - * successfully. + * Get the port that this client session is connected to. If the client + * is not connected, this function will return 0. Otherwise, it will + * return the port from the list server_list that the client is connected + * to. * - * @return The port that this client session has been created with. + * @return The port that this client is connected to */ std::uint16_t get_port() const; + /** + * @brief Get the remote endpoint that this client session is connected to. + * + * Get the remote endpoint that this client session is connected to. Only + * valid, if the client session is actually connected to a server. If a + * hostname was given, this function will return the resolved IP address. + */ + asio::ip::tcp::endpoint get_remote_endpoint() const; + /** * @brief Get the state of this client session. * diff --git a/ecal/service/ecal_service/include/ecal/service/server_manager.h b/ecal/service/ecal_service/include/ecal/service/server_manager.h index fa93457a46..f54c4bb7fa 100644 --- a/ecal/service/ecal_service/include/ecal/service/server_manager.h +++ b/ecal/service/ecal_service/include/ecal/service/server_manager.h @@ -23,10 +23,14 @@ #include #include #include - -#include #include +#include + +#include + +#include // IWYU pragma: export + namespace eCAL { namespace service diff --git a/ecal/service/ecal_service/src/client_manager.cpp b/ecal/service/ecal_service/src/client_manager.cpp index 3843fb07c0..57431782a1 100644 --- a/ecal/service/ecal_service/src/client_manager.cpp +++ b/ecal/service/ecal_service/src/client_manager.cpp @@ -17,14 +17,22 @@ * ========================= eCAL LICENSE ================================= */ +#include + #include #include -#include #include #include #include #include +#include + +#include +#include +#include +#include + namespace eCAL { namespace service @@ -52,10 +60,9 @@ namespace eCAL /////////////////////////////////////////////////////// // Public API /////////////////////////////////////////////////////// - std::shared_ptr ClientManager::create_client(std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const ClientSession::EventCallbackT& event_callback) + std::shared_ptr ClientManager::create_client(std::uint8_t protocol_version + , const std::vector>& server_list + , const ClientSession::EventCallbackT& event_callback) { const std::lock_guard lock(client_manager_mutex_); if (stopped_) @@ -74,7 +81,7 @@ namespace eCAL } }; - auto client = ClientSession::create(io_context_, protocol_version, address, port, event_callback, logger_, deleter); + auto client = ClientSession::create(io_context_, protocol_version, server_list, event_callback, logger_, deleter); sessions_.emplace(client.get(), client); return client; } diff --git a/ecal/service/ecal_service/src/client_session.cpp b/ecal/service/ecal_service/src/client_session.cpp index c3a032f0c3..9a84ddbc50 100644 --- a/ecal/service/ecal_service/src/client_session.cpp +++ b/ecal/service/ecal_service/src/client_session.cpp @@ -17,11 +17,21 @@ * ========================= eCAL LICENSE ================================= */ -#include +#include #include + +#include #include #include #include +#include +#include + +#include + +#include +#include +#include #include "client_session_impl_v1.h" #include "client_session_impl_v0.h" @@ -31,13 +41,12 @@ namespace eCAL { namespace service { - std::shared_ptr ClientSession::create(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger - , const DeleteCallbackT& delete_callback) + std::shared_ptr ClientSession::create(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger + , const DeleteCallbackT& delete_callback) { auto deleter = [delete_callback](ClientSession* session) { @@ -45,43 +54,40 @@ namespace eCAL delete session; // NOLINT(cppcoreguidelines-owning-memory) }; - return std::shared_ptr(new ClientSession(io_context, protocol_version, address, port, event_callback, logger), deleter); + return std::shared_ptr(new ClientSession(io_context, protocol_version, server_list, event_callback, logger), deleter); } - std::shared_ptr ClientSession::create(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger) + std::shared_ptr ClientSession::create(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger) { - return std::shared_ptr(new ClientSession(io_context, protocol_version, address, port, event_callback, logger)); + return std::shared_ptr(new ClientSession(io_context, protocol_version, server_list, event_callback, logger)); } - std::shared_ptr ClientSession::create(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const DeleteCallbackT& delete_callback) + std::shared_ptr ClientSession::create(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const DeleteCallbackT& delete_callback) { - return ClientSession::create(io_context, protocol_version, address, port, event_callback, default_logger("Service Client"), delete_callback); + return ClientSession::create(io_context, protocol_version, server_list, event_callback, default_logger("Service Client"), delete_callback); } - ClientSession::ClientSession(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger) + ClientSession::ClientSession(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger) { if (protocol_version == 0) { - impl_ = ClientSessionV0::create(io_context, address, port, event_callback, logger); + impl_ = ClientSessionV0::create(io_context, server_list, event_callback, logger); } else { - impl_ = ClientSessionV1::create(io_context, address, port, event_callback, logger); + impl_ = ClientSessionV1::create(io_context, server_list, event_callback, logger); } } @@ -140,11 +146,12 @@ namespace eCAL } } - State ClientSession::get_state() const { return impl_->get_state(); } - std::uint8_t ClientSession::get_accepted_protocol_version() const { return impl_->get_accepted_protocol_version(); } - int ClientSession::get_queue_size() const { return impl_->get_queue_size(); } - std::string ClientSession::get_address() const { return impl_->get_address(); } - std::uint16_t ClientSession::get_port() const { return impl_->get_port(); } - void ClientSession::stop() { impl_->stop(); } + State ClientSession::get_state() const { return impl_->get_state(); } + std::uint8_t ClientSession::get_accepted_protocol_version() const { return impl_->get_accepted_protocol_version(); } + int ClientSession::get_queue_size() const { return impl_->get_queue_size(); } + std::string ClientSession::get_host() const { return impl_->get_host(); } + asio::ip::tcp::endpoint ClientSession::get_remote_endpoint() const { return impl_->get_remote_endpoint(); } + std::uint16_t ClientSession::get_port() const { return impl_->get_port(); } + void ClientSession::stop() { impl_->stop(); } } // namespace service } // namespace eCAL diff --git a/ecal/service/ecal_service/src/client_session_impl_base.h b/ecal/service/ecal_service/src/client_session_impl_base.h index df87e1eedb..6db29e96c1 100644 --- a/ecal/service/ecal_service/src/client_session_impl_base.h +++ b/ecal/service/ecal_service/src/client_session_impl_base.h @@ -32,7 +32,6 @@ #pragma warning(pop) #endif -#include #include #include @@ -74,8 +73,9 @@ namespace eCAL public: virtual bool async_call_service(const std::shared_ptr& request, const ResponseCallbackT& response_callback) = 0; - virtual std::string get_address() const = 0; - virtual std::uint16_t get_port() const = 0; + virtual std::string get_host() const = 0; + virtual std::uint16_t get_port() const = 0; + virtual asio::ip::tcp::endpoint get_remote_endpoint() const = 0; virtual State get_state() const = 0; virtual std::uint8_t get_accepted_protocol_version() const = 0; diff --git a/ecal/service/ecal_service/src/client_session_impl_v0.cpp b/ecal/service/ecal_service/src/client_session_impl_v0.cpp index 739e14b5de..de123b9e3f 100644 --- a/ecal/service/ecal_service/src/client_session_impl_v0.cpp +++ b/ecal/service/ecal_service/src/client_session_impl_v0.cpp @@ -18,19 +18,29 @@ */ #include "client_session_impl_v0.h" +#include "client_session_impl_base.h" #include "protocol_v0.h" +#include "protocol_layout.h" #include "log_helpers.h" #include "log_defs.h" +#include +#include +#include +#include + #include #include -#include #include #include +#include #include +#include #include +#include + namespace eCAL { namespace service @@ -38,27 +48,30 @@ namespace eCAL ///////////////////////////////////// // Constructor, Destructor, Create ///////////////////////////////////// - std::shared_ptr ClientSessionV0::create(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger) + std::shared_ptr ClientSessionV0::create(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger) { - std::shared_ptr instance(new ClientSessionV0(io_context, address, port, event_callback, logger)); + std::shared_ptr instance(new ClientSessionV0(io_context, server_list, event_callback, logger)); + + // Throw exception, if the server list is empty + if (server_list.empty()) + { + throw std::invalid_argument("Server list must not be empty"); + } - instance->resolve_endpoint(); + instance->resolve_endpoint(0); return instance; } - ClientSessionV0::ClientSessionV0(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger) + ClientSessionV0::ClientSessionV0(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger) : ClientSessionBase(io_context, event_callback) - , address_ (address) - , port_ (port) + , server_list_ (server_list) , service_call_queue_strand_(*io_context) , resolver_ (*io_context) , logger_ (logger) @@ -78,21 +91,44 @@ namespace eCAL ////////////////////////////////////// // Connection establishement ////////////////////////////////////// - void ClientSessionV0::resolve_endpoint() + void ClientSessionV0::resolve_endpoint(size_t server_list_index) { - ECAL_SERVICE_LOG_DEBUG(logger_, "Resolving endpoint [" + address_ + ":" + std::to_string(port_) + "]..."); + ECAL_SERVICE_LOG_DEBUG(logger_, "Resolving endpoint [" + server_list_[server_list_index].first + ":" + std::to_string(server_list_[server_list_index].second) + "]..."); - const asio::ip::tcp::resolver::query query(address_, std::to_string(port_)); + const asio::ip::tcp::resolver::query query(server_list_[server_list_index].first, std::to_string(server_list_[server_list_index].second)); resolver_.async_resolve(query - , service_call_queue_strand_.wrap([me = enable_shared_from_this::shared_from_this()] + , service_call_queue_strand_.wrap([me = enable_shared_from_this::shared_from_this(), server_list_index] (asio::error_code ec, const asio::ip::tcp::resolver::iterator& resolved_endpoints) { if (ec) { - const std::string message = "Failed resolving endpoint [" + me->address_ + ":" + std::to_string(me->port_) + "]: " + ec.message(); - me->logger_(LogLevel::Error, message); - me->handle_connection_loss_error(message); +#if ECAL_SERVICE_LOG_DEBUG_ENABLED + { + const std::string message = "Failed resolving endpoint [" + me->server_list_[server_list_index].first + ":" + std::to_string(me->server_list_[server_list_index].second) + "]: " + ec.message(); + ECAL_SERVICE_LOG_DEBUG(me->logger_, message); + } +#endif + + if (server_list_index + 1 < me->server_list_.size()) + { + // Try next possible endpoint + me->resolve_endpoint(server_list_index + 1); + } + else + { + std::string message = "Failed resolving any endpoint: "; + for (size_t j = 0; j < me->server_list_.size(); ++j) + { + message += me->server_list_[j].first + ":" + std::to_string(me->server_list_[j].second); + if (j + 1 < me->server_list_.size()) + { + message += ", "; + } + } + me->logger_(LogLevel::Error, message); + me->handle_connection_loss_error(message); + } return; } else @@ -100,7 +136,7 @@ namespace eCAL #if ECAL_SERVICE_LOG_DEBUG_VERBOSE_ENABLED // Verbose-debug log of all endpoints { - std::string endpoints_str = "Resolved endpoints for " + me->address_ + ": "; + std::string endpoints_str = "Resolved endpoints for " + me->server_list_[server_list_index].first + ": "; for (auto it = resolved_endpoints; it != asio::ip::tcp::resolver::iterator(); ++it) { endpoints_str += endpoint_to_string(*it) + ", "; @@ -108,12 +144,12 @@ namespace eCAL ECAL_SERVICE_LOG_DEBUG_VERBOSE(me->logger_, endpoints_str); } #endif //ECAL_SERVICE_LOG_DEBUG_VERBOSE_ENABLED - me->connect_to_endpoint(resolved_endpoints); + me->connect_to_endpoint(resolved_endpoints, server_list_index); } })); } - void ClientSessionV0::connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints) + void ClientSessionV0::connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints, size_t server_list_index) { // Convert the resolved_endpoints iterator to an endpoint sequence // (i.e. a vector of endpoints) @@ -126,14 +162,36 @@ namespace eCAL const std::lock_guard socket_lock(socket_mutex_); asio::async_connect(socket_ , *endpoint_sequence - , service_call_queue_strand_.wrap([me = shared_from_this(), endpoint_sequence](asio::error_code ec, const asio::ip::tcp::endpoint& endpoint) + , service_call_queue_strand_.wrap([me = shared_from_this(), endpoint_sequence, server_list_index](asio::error_code ec, const asio::ip::tcp::endpoint& endpoint) { (void)endpoint; if (ec) { - const std::string message = "Failed to connect to endpoint [" + me->address_ + ":" + std::to_string(me->port_) + "]: " + ec.message(); - me->logger_(LogLevel::Error, message); - me->handle_connection_loss_error(message); + { + // Log an error + const std::string message = "Failed to connect to endpoint [" + me->chosen_endpoint_.first + ":" + std::to_string(me->chosen_endpoint_.second) + "]: " + ec.message(); + me->logger_(LogLevel::Error, message); + } + + // If there are more servers available, try the next one + if (server_list_index + 1 < me->server_list_.size()) + { + me->resolve_endpoint(server_list_index + 1); + } + else + { + std::string message = "Failed to connect to any endpoint: "; + for (size_t j = 0; j < me->server_list_.size(); ++j) + { + message += me->server_list_[j].first + ":" + std::to_string(me->server_list_[j].second); + if (j + 1 < me->server_list_.size()) + { + message += ", "; + } + } + me->logger_(LogLevel::Error, message); + me->handle_connection_loss_error(message); + } return; } else @@ -148,7 +206,7 @@ namespace eCAL asio::error_code socket_option_ec; { const std::lock_guard socket_lock(me->socket_mutex_); - me->socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); + me->socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); // NOLINT(bugprone-unused-return-value) -> We already get the value from the ec parameter } if (socket_option_ec) { @@ -156,6 +214,12 @@ namespace eCAL } } + { + // Set the chosen endpoint + const std::lock_guard chosen_endpoint_lock(me->chosen_endpoint_mutex_); + me->chosen_endpoint_ = me->server_list_[server_list_index]; + } + const std::string message = "Connected to server. Using protocol version 0."; me->logger_(LogLevel::Info, "[" + get_connection_info_string(me->socket_) + "] " + message); @@ -354,14 +418,29 @@ namespace eCAL // Status API ////////////////////////////////////// - std::string ClientSessionV0::get_address() const + std::string ClientSessionV0::get_host() const { - return address_; + const std::lock_guard chosen_endpoint_lock(chosen_endpoint_mutex_); + return chosen_endpoint_.first; } std::uint16_t ClientSessionV0::get_port() const { - return port_; + const std::lock_guard chosen_endpoint_lock(chosen_endpoint_mutex_); + return chosen_endpoint_.second; + } + + asio::ip::tcp::endpoint ClientSessionV0::get_remote_endpoint() const + { + // form remote endpoint string + { + asio::error_code ec; + auto endpoint = socket_.remote_endpoint(ec); + if (!ec) + return endpoint; + else + return asio::ip::tcp::endpoint(); + } } State ClientSessionV0::get_state() const @@ -480,7 +559,7 @@ namespace eCAL { // Set the stopped_by_user_ flag to true, so that the async operations stop enqueuing new service calls. - std::lock_guard service_state_lock(service_state_mutex_); + const std::lock_guard service_state_lock(service_state_mutex_); stopped_by_user_ = true; } @@ -500,12 +579,12 @@ namespace eCAL { { asio::error_code ec; - socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); + socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); // NOLINT(bugprone-unused-return-value) -> we already get the value by the ec parameter } { asio::error_code ec; - socket_.close(ec); + socket_.close(ec); // NOLINT(bugprone-unused-return-value) -> we already get the value by the ec parameter } } } diff --git a/ecal/service/ecal_service/src/client_session_impl_v0.h b/ecal/service/ecal_service/src/client_session_impl_v0.h index 96017f1be3..326ad1b648 100644 --- a/ecal/service/ecal_service/src/client_session_impl_v0.h +++ b/ecal/service/ecal_service/src/client_session_impl_v0.h @@ -20,13 +20,20 @@ #pragma once #include "client_session_impl_base.h" -#include -#include +#include +#include #include #include #include #include +#include +#include + +#include + +#include +#include namespace eCAL { @@ -50,18 +57,16 @@ namespace eCAL // Constructor, Destructor, Create ///////////////////////////////////// public: - static std::shared_ptr create(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger = default_logger("Service Client V1")); + static std::shared_ptr create(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger = default_logger("Service Client V1")); protected: - ClientSessionV0(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger); + ClientSessionV0(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger); public: // Delete copy / move constructor and assignment operator @@ -77,8 +82,8 @@ namespace eCAL // Connection establishement ////////////////////////////////////// private: - void resolve_endpoint(); - void connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints); + void resolve_endpoint(size_t server_list_index); + void connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints, size_t server_list_index); ////////////////////////////////////// // Service calls @@ -94,8 +99,10 @@ namespace eCAL // Status API ////////////////////////////////////// public: - std::string get_address() const override; - std::uint16_t get_port() const override; + std::string get_host() const override; + std::uint16_t get_port() const override; + asio::ip::tcp::endpoint get_remote_endpoint() const override; + State get_state() const override; std::uint8_t get_accepted_protocol_version() const override; int get_queue_size() const override; @@ -137,8 +144,10 @@ namespace eCAL ////////////////////////////////////// private: - const std::string address_; //!< The original address that this client was created with. - const std::uint16_t port_; //!< The original port that this client was created with. + const std::vector> server_list_; //!< The list of servers that this client was created with. They will be tried in order. + + mutable std::mutex chosen_endpoint_mutex_; //!< Protects the chosen_endpoint_ variable. + std::pair chosen_endpoint_; //!< The endpoint that the client is currently connected to. Protected by chosen_endpoint_mutex_. asio::io_context::strand service_call_queue_strand_; asio::ip::tcp::resolver resolver_; diff --git a/ecal/service/ecal_service/src/client_session_impl_v1.cpp b/ecal/service/ecal_service/src/client_session_impl_v1.cpp index 25cd2e7a69..576a3f3c5b 100644 --- a/ecal/service/ecal_service/src/client_session_impl_v1.cpp +++ b/ecal/service/ecal_service/src/client_session_impl_v1.cpp @@ -18,20 +18,35 @@ */ #include "client_session_impl_v1.h" +#include "client_session_impl_base.h" #include "protocol_v1.h" +#include "protocol_layout.h" #include "log_helpers.h" #include "log_defs.h" #include #include -#include #include #include +#include #include #include #include +#include + +#include +#include +#include +#include + +#ifdef WIN32 + #include +#else + #include +#endif + namespace eCAL { namespace service @@ -42,27 +57,30 @@ namespace eCAL ///////////////////////////////////// // Constructor, Destructor, Create ///////////////////////////////////// - std::shared_ptr ClientSessionV1::create(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger) + std::shared_ptr ClientSessionV1::create(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger) { - std::shared_ptr instance(new ClientSessionV1(io_context, address, port, event_callback, logger)); + std::shared_ptr instance(new ClientSessionV1(io_context, server_list, event_callback, logger)); + + // Throw exception, if the server list is empty + if (server_list.empty()) + { + throw std::invalid_argument("Server list must not be empty"); + } - instance->resolve_endpoint(); + instance->resolve_endpoint(0); return instance; } - ClientSessionV1::ClientSessionV1(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger) + ClientSessionV1::ClientSessionV1(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger) : ClientSessionBase(io_context, event_callback) - , address_ (address) - , port_ (port) + , server_list_ (server_list) , service_call_queue_strand_(*io_context) , resolver_ (*io_context) , logger_ (logger) @@ -83,21 +101,44 @@ namespace eCAL ////////////////////////////////////// // Connection establishement ////////////////////////////////////// - void ClientSessionV1::resolve_endpoint() + void ClientSessionV1::resolve_endpoint(size_t server_list_index) { - ECAL_SERVICE_LOG_DEBUG(logger_, "Resolving endpoint [" + address_ + ":" + std::to_string(port_) + "]..."); + ECAL_SERVICE_LOG_DEBUG(logger_, "Resolving endpoint [" + server_list_[server_list_index].first + ":" + std::to_string(server_list_[server_list_index].second) + "]..."); - const asio::ip::tcp::resolver::query query(address_, std::to_string(port_)); + const asio::ip::tcp::resolver::query query(server_list_[server_list_index].first, std::to_string(server_list_[server_list_index].second)); resolver_.async_resolve(query - , service_call_queue_strand_.wrap([me = enable_shared_from_this::shared_from_this()] + , service_call_queue_strand_.wrap([me = enable_shared_from_this::shared_from_this(), server_list_index] (asio::error_code ec, const asio::ip::tcp::resolver::iterator& resolved_endpoints) { if (ec) { - const std::string message = "Failed resolving endpoint [" + me->address_ + ":" + std::to_string(me->port_) + "]: " + ec.message(); - me->logger_(LogLevel::Error, message); - me->handle_connection_loss_error(message); +#if ECAL_SERVICE_LOG_DEBUG_ENABLED + { + const std::string message = "Failed resolving endpoint [" + me->server_list_[server_list_index].first + ":" + std::to_string(me->server_list_[server_list_index].second) + "]: " + ec.message(); + ECAL_SERVICE_LOG_DEBUG(me->logger_, message); + } +#endif + + if (server_list_index + 1 < me->server_list_.size()) + { + // Try next possible endpoint + me->resolve_endpoint(server_list_index + 1); + } + else + { + std::string message = "Failed resolving any endpoint: "; + for (size_t j = 0; j < me->server_list_.size(); ++j) + { + message += me->server_list_[j].first + ":" + std::to_string(me->server_list_[j].second); + if (j + 1 < me->server_list_.size()) + { + message += ", "; + } + } + me->logger_(LogLevel::Error, message); + me->handle_connection_loss_error(message); + } return; } else @@ -105,7 +146,7 @@ namespace eCAL #if ECAL_SERVICE_LOG_DEBUG_VERBOSE_ENABLED // Verbose-debug log of all endpoints { - std::string endpoints_str = "Resolved endpoints for " + me->address_ + ": "; + std::string endpoints_str = "Resolved endpoints for " + me->server_list_[server_list_index].first + ": "; for (auto it = resolved_endpoints; it != asio::ip::tcp::resolver::iterator(); ++it) { endpoints_str += endpoint_to_string(*it) + ", "; @@ -113,12 +154,12 @@ namespace eCAL ECAL_SERVICE_LOG_DEBUG_VERBOSE(me->logger_, endpoints_str); } #endif //ECAL_SERVICE_LOG_DEBUG_VERBOSE_ENABLED - me->connect_to_endpoint(resolved_endpoints); + me->connect_to_endpoint(resolved_endpoints, server_list_index); } })); } - void ClientSessionV1::connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints) + void ClientSessionV1::connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints, size_t server_list_index) { // Convert the resolved_endpoints iterator to an endpoint sequence // (i.e. a vector of endpoints) @@ -131,14 +172,36 @@ namespace eCAL const std::lock_guard socket_lock(socket_mutex_); asio::async_connect(socket_ , *endpoint_sequence - , service_call_queue_strand_.wrap([me = shared_from_this(), endpoint_sequence](asio::error_code ec, const asio::ip::tcp::endpoint& endpoint) + , service_call_queue_strand_.wrap([me = shared_from_this(), endpoint_sequence, server_list_index](asio::error_code ec, const asio::ip::tcp::endpoint& endpoint) { (void)endpoint; if (ec) { - const std::string message = "Failed to connect to endpoint [" + me->address_ + ":" + std::to_string(me->port_) + "]: " + ec.message(); - me->logger_(LogLevel::Error, message); - me->handle_connection_loss_error(message); + { + // Log an error + const std::string message = "Failed to connect to endpoint [" + me->chosen_endpoint_.first + ":" + std::to_string(me->chosen_endpoint_.second) + "]: " + ec.message(); + me->logger_(LogLevel::Error, message); + } + + // If there are more servers available, try the next one + if (server_list_index + 1 < me->server_list_.size()) + { + me->resolve_endpoint(server_list_index + 1); + } + else + { + std::string message = "Failed to connect to any endpoint: "; + for (size_t j = 0; j < me->server_list_.size(); ++j) + { + message += me->server_list_[j].first + ":" + std::to_string(me->server_list_[j].second); + if (j + 1 < me->server_list_.size()) + { + message += ", "; + } + } + me->logger_(LogLevel::Error, message); + me->handle_connection_loss_error(message); + } return; } else @@ -153,7 +216,7 @@ namespace eCAL asio::error_code socket_option_ec; { const std::lock_guard socket_lock(me->socket_mutex_); - me->socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); + me->socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); // NOLINT(bugprone-unused-return-value) -> We already get the value from the ec parameter } if (socket_option_ec) { @@ -161,6 +224,11 @@ namespace eCAL } } + { + const std::lock_guard chosen_endpoint_lock(me->chosen_endpoint_mutex_); + me->chosen_endpoint_ = me->server_list_[server_list_index]; + } + // Start sending the protocol handshake to the server. This will tell us the actual protocol version. me->send_protocol_handshake_request(); } @@ -477,14 +545,29 @@ namespace eCAL // Status API ////////////////////////////////////// - std::string ClientSessionV1::get_address() const + std::string ClientSessionV1::get_host() const { - return address_; + const std::lock_guard chosen_endpoint_lock(chosen_endpoint_mutex_); + return chosen_endpoint_.first; } std::uint16_t ClientSessionV1::get_port() const { - return port_; + const std::lock_guard chosen_endpoint_lock(chosen_endpoint_mutex_); + return chosen_endpoint_.second; + } + + asio::ip::tcp::endpoint ClientSessionV1::get_remote_endpoint() const + { + // form remote endpoint string + { + asio::error_code ec; + auto endpoint = socket_.remote_endpoint(ec); + if (!ec) + return endpoint; + else + return asio::ip::tcp::endpoint(); + } } State ClientSessionV1::get_state() const @@ -603,7 +686,7 @@ namespace eCAL { // Set the stopped_by_user_ flag to true, so that the async operations stop enqueuing new service calls. - std::lock_guard service_state_lock(service_state_mutex_); + const std::lock_guard service_state_lock(service_state_mutex_); stopped_by_user_ = true; } @@ -623,12 +706,12 @@ namespace eCAL { { asio::error_code ec; - socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); + socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); // NOLINT(bugprone-unused-return-value) -> we already get the return value from the ec parameter } { asio::error_code ec; - socket_.close(ec); + socket_.close(ec); // NOLINT(bugprone-unused-return-value) -> we already get the return value from the ec parameter } } } diff --git a/ecal/service/ecal_service/src/client_session_impl_v1.h b/ecal/service/ecal_service/src/client_session_impl_v1.h index ebd1a92529..c95d360f1e 100644 --- a/ecal/service/ecal_service/src/client_session_impl_v1.h +++ b/ecal/service/ecal_service/src/client_session_impl_v1.h @@ -20,14 +20,21 @@ #pragma once #include "client_session_impl_base.h" + #include +#include #include -#include - #include #include #include #include +#include +#include + +#include + +#include +#include namespace eCAL { @@ -51,18 +58,16 @@ namespace eCAL // Constructor, Destructor, Create ///////////////////////////////////// public: - static std::shared_ptr create(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger_ = default_logger("Service Client V1")); + static std::shared_ptr create(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger_ = default_logger("Service Client V1")); protected: - ClientSessionV1(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger); + ClientSessionV1(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger); public: // Delete copy / move constructor and assignment operator @@ -78,8 +83,8 @@ namespace eCAL // Connection establishement ////////////////////////////////////// private: - void resolve_endpoint(); - void connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints); + void resolve_endpoint(size_t server_list_index); + void connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints, size_t server_list_index); void send_protocol_handshake_request(); void receive_protocol_handshake_response(); @@ -98,8 +103,10 @@ namespace eCAL // Status API ////////////////////////////////////// public: - std::string get_address() const override; - std::uint16_t get_port() const override; + std::string get_host() const override; + std::uint16_t get_port() const override; + asio::ip::tcp::endpoint get_remote_endpoint() const override; + State get_state() const override; std::uint8_t get_accepted_protocol_version() const override; int get_queue_size() const override; @@ -144,8 +151,10 @@ namespace eCAL static constexpr std::uint8_t MIN_SUPPORTED_PROTOCOL_VERSION = 1; static constexpr std::uint8_t MAX_SUPPORTED_PROTOCOL_VERSION = 1; - const std::string address_; //!< The original address that this client was created with. - const std::uint16_t port_; //!< The original port that this client was created with. + const std::vector> server_list_; //!< The list of servers that this client was created with. They will be tried in order. + + mutable std::mutex chosen_endpoint_mutex_; //!< Protects the chosen_endpoint_ variable. + std::pair chosen_endpoint_; //!< The endpoint that the client is currently connected to. Protected by chosen_endpoint_mutex_. asio::io_context::strand service_call_queue_strand_; asio::ip::tcp::resolver resolver_; diff --git a/ecal/service/ecal_service/src/log_defs.h b/ecal/service/ecal_service/src/log_defs.h index 1ca102e8c0..1b48d85905 100644 --- a/ecal/service/ecal_service/src/log_defs.h +++ b/ecal/service/ecal_service/src/log_defs.h @@ -20,8 +20,6 @@ #pragma once -#include - ///////////////////////////////////////////// // Enable / disable debug logging ///////////////////////////////////////////// diff --git a/ecal/service/ecal_service/src/protocol_v0.cpp b/ecal/service/ecal_service/src/protocol_v0.cpp index 54d231b4a4..c66d297e73 100644 --- a/ecal/service/ecal_service/src/protocol_v0.cpp +++ b/ecal/service/ecal_service/src/protocol_v0.cpp @@ -26,6 +26,15 @@ #include #include +#include +#include + +#ifdef WIN32 + #include +#else + #include +#endif + namespace eCAL { namespace service diff --git a/ecal/service/ecal_service/src/protocol_v1.cpp b/ecal/service/ecal_service/src/protocol_v1.cpp index 3ce1395957..7be282cbe7 100644 --- a/ecal/service/ecal_service/src/protocol_v1.cpp +++ b/ecal/service/ecal_service/src/protocol_v1.cpp @@ -27,6 +27,14 @@ #include #include +#include + +#ifdef WIN32 + #include +#else + #include +#endif + namespace eCAL { namespace service diff --git a/ecal/service/ecal_service/src/server.cpp b/ecal/service/ecal_service/src/server.cpp index c5c4002f15..7e6c726d3c 100644 --- a/ecal/service/ecal_service/src/server.cpp +++ b/ecal/service/ecal_service/src/server.cpp @@ -17,12 +17,15 @@ * ========================= eCAL LICENSE ================================= */ -#include #include -#include +#include #include +#include + +#include + #include "server_impl.h" namespace eCAL diff --git a/ecal/service/ecal_service/src/server_impl.cpp b/ecal/service/ecal_service/src/server_impl.cpp index 28da90c43e..f1f077baee 100644 --- a/ecal/service/ecal_service/src/server_impl.cpp +++ b/ecal/service/ecal_service/src/server_impl.cpp @@ -24,9 +24,16 @@ #include #include +#include + +#include "server_session_impl_base.h" #include "server_session_impl_v1.h" #include "server_session_impl_v0.h" +#include +#include +#include + #include "log_defs.h" namespace eCAL @@ -137,7 +144,7 @@ namespace eCAL { const std::lock_guard acceptor_lock(acceptor_mutex_); - acceptor_.open(endpoint.protocol(), ec); + acceptor_.open(endpoint.protocol(), ec); // NOLINT(bugprone-unused-return-value) -> We already get the return value rom the ec parameter } if (ec) { @@ -152,7 +159,7 @@ namespace eCAL { asio::error_code ec; { - acceptor_.set_option(asio::ip::tcp::acceptor::reuse_address(true), ec); + acceptor_.set_option(asio::ip::tcp::acceptor::reuse_address(true), ec); // NOLINT(bugprone-unused-return-value) -> We already get the value from the ec parameter const std::lock_guard acceptor_lock(acceptor_mutex_); } if (ec) @@ -168,7 +175,7 @@ namespace eCAL asio::error_code ec; { const std::lock_guard acceptor_lock(acceptor_mutex_); - acceptor_.bind(endpoint, ec); + acceptor_.bind(endpoint, ec); // NOLINT(bugprone-unused-return-value) -> We already get the return value rom the ec parameter } if (ec) { @@ -183,7 +190,7 @@ namespace eCAL asio::error_code ec; { const std::lock_guard acceptor_lock(acceptor_mutex_); - acceptor_.listen(asio::socket_base::max_listen_connections, ec); + acceptor_.listen(asio::socket_base::max_listen_connections, ec); // NOLINT(bugprone-unused-return-value) -> We already get the return value rom the ec parameter } if (ec) { @@ -296,7 +303,7 @@ namespace eCAL if (acceptor_.is_open()) { asio::error_code ec; - acceptor_.close(ec); + acceptor_.close(ec); // NOLINT(bugprone-unused-return-value) -> We already get the return value rom the ec parameter } } diff --git a/ecal/service/ecal_service/src/server_impl.h b/ecal/service/ecal_service/src/server_impl.h index 9b33edd76d..6ef2355d63 100644 --- a/ecal/service/ecal_service/src/server_impl.h +++ b/ecal/service/ecal_service/src/server_impl.h @@ -19,9 +19,7 @@ #pragma once -#include #include -#include #include #include #include @@ -32,7 +30,6 @@ #pragma warning(disable: 4834) #endif #include -#include #ifdef _MSC_VER #pragma warning(pop) #endif diff --git a/ecal/service/ecal_service/src/server_manager.cpp b/ecal/service/ecal_service/src/server_manager.cpp index 0b462412c9..2fad646d96 100644 --- a/ecal/service/ecal_service/src/server_manager.cpp +++ b/ecal/service/ecal_service/src/server_manager.cpp @@ -17,13 +17,19 @@ * ========================= eCAL LICENSE ================================= */ +#include + #include #include -#include #include #include #include +#include + +#include +#include + namespace eCAL { namespace service diff --git a/ecal/service/ecal_service/src/server_session_impl_base.h b/ecal/service/ecal_service/src/server_session_impl_base.h index 1d84acf6c2..ff663e28cf 100644 --- a/ecal/service/ecal_service/src/server_session_impl_base.h +++ b/ecal/service/ecal_service/src/server_session_impl_base.h @@ -23,18 +23,16 @@ #include #include -#include #ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable: 4834) +#pragma warning(push) +#pragma warning(disable : 4834) #endif #include -#include #ifdef _MSC_VER - #pragma warning(pop) +#pragma warning(pop) #endif #include diff --git a/ecal/service/ecal_service/src/server_session_impl_v0.cpp b/ecal/service/ecal_service/src/server_session_impl_v0.cpp index d06dfe050c..a96f6ec8ac 100644 --- a/ecal/service/ecal_service/src/server_session_impl_v0.cpp +++ b/ecal/service/ecal_service/src/server_session_impl_v0.cpp @@ -18,11 +18,12 @@ */ #include "server_session_impl_v0.h" +#include "server_session_impl_base.h" #include "log_helpers.h" #include "log_defs.h" - #include "protocol_layout.h" + #include #include #include @@ -30,6 +31,18 @@ #include #include +#include + +#include +#include +#include + +#ifdef WIN32 + #include +#else + #include +#endif + /////////////////////////////////////////////// // Create, Constructor, Destructor /////////////////////////////////////////////// @@ -104,7 +117,7 @@ namespace eCAL asio::error_code socket_option_ec; { const std::lock_guard socket_lock(socket_mutex_); - socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); + socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); // NOLINT(bugprone-unused-return-value) -> We already get the return value rom the ec parameter } if (socket_option_ec) { @@ -131,13 +144,13 @@ namespace eCAL { // Shutdown the socket asio::error_code ec; - socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); + socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); // NOLINT(bugprone-unused-return-value) -> We already get the return value rom the ec parameter } { // Close the socket asio::error_code ec; - socket_.close(ec); + socket_.close(ec); // NOLINT(bugprone-unused-return-value) -> We already get the return value rom the ec parameter } } } diff --git a/ecal/service/ecal_service/src/server_session_impl_v0.h b/ecal/service/ecal_service/src/server_session_impl_v0.h index 4c4f93dbe8..af3c33ea54 100644 --- a/ecal/service/ecal_service/src/server_session_impl_v0.h +++ b/ecal/service/ecal_service/src/server_session_impl_v0.h @@ -22,12 +22,14 @@ #include "server_session_impl_base.h" #include #include +#include +#include + +#include + #include #include - #include -#include -#include namespace eCAL { diff --git a/ecal/service/ecal_service/src/server_session_impl_v1.cpp b/ecal/service/ecal_service/src/server_session_impl_v1.cpp index 3633a2445c..568e524aef 100644 --- a/ecal/service/ecal_service/src/server_session_impl_v1.cpp +++ b/ecal/service/ecal_service/src/server_session_impl_v1.cpp @@ -18,11 +18,13 @@ */ #include "server_session_impl_v1.h" +#include "server_session_impl_base.h" #include "protocol_v1.h" - +#include "protocol_layout.h" #include "log_defs.h" #include "log_helpers.h" + #include #include #include @@ -30,6 +32,18 @@ #include #include +#include + +#include +#include +#include + +#ifdef WIN32 + #include +#else + #include +#endif + /////////////////////////////////////////////// // Create, Constructor, Destructor /////////////////////////////////////////////// @@ -88,7 +102,7 @@ namespace eCAL asio::error_code socket_option_ec; { const std::lock_guard socket_lock(socket_mutex_); - socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); + socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); // NOLINT(bugprone-unused-return-value) -> We already get the value from the ec parameter } if (socket_option_ec) { @@ -108,12 +122,12 @@ namespace eCAL { // Shutdown the socket asio::error_code ec; - socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); + socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); // NOLINT(bugprone-unused-return-value) -> We already get the value from the ec parameter } { // Close the socket asio::error_code ec; - socket_.close(ec); + socket_.close(ec); // NOLINT(bugprone-unused-return-value) -> We already get the value from the ec parameter } } } diff --git a/ecal/service/ecal_service/src/server_session_impl_v1.h b/ecal/service/ecal_service/src/server_session_impl_v1.h index d61e2ecc1e..4a233270bb 100644 --- a/ecal/service/ecal_service/src/server_session_impl_v1.h +++ b/ecal/service/ecal_service/src/server_session_impl_v1.h @@ -20,14 +20,17 @@ #pragma once #include "server_session_impl_base.h" + #include #include +#include +#include + +#include + #include #include - #include -#include -#include namespace eCAL { diff --git a/ecal/service/sample/CMakeLists.txt b/ecal/service/samples/sample_client/CMakeLists.txt similarity index 97% rename from ecal/service/sample/CMakeLists.txt rename to ecal/service/samples/sample_client/CMakeLists.txt index 0296bbc6e4..90c4cd4a67 100644 --- a/ecal/service/sample/CMakeLists.txt +++ b/ecal/service/samples/sample_client/CMakeLists.txt @@ -16,7 +16,7 @@ # # ========================= eCAL LICENSE ================================= -project(service_sample) +project(service_sample_client) find_package(Threads REQUIRED) diff --git a/ecal/service/samples/sample_client/src/main.cpp b/ecal/service/samples/sample_client/src/main.cpp new file mode 100644 index 0000000000..403f981104 --- /dev/null +++ b/ecal/service/samples/sample_client/src/main.cpp @@ -0,0 +1,160 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2023 Continental Corporation + * + * 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. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +void print_usage(const std::string& arg0) +{ + std::cout << "Usage: " << arg0 << " Host:Port [Host:Port ...] [--protocol-version ]" << std::endl; +} + +std::pair parse_server(const std::string& server) +{ + // Find the last ':' to split into host and port + auto pos = server.find_last_of(':'); + if (pos == std::string::npos) + { + throw std::runtime_error("Invalid server: " + server); + } + + return std::make_pair(server.substr(0, pos), static_cast(std::stoi(server.substr(pos + 1)))); +} + +int main(int argc, char** argv) +{ + // Convert command line arguments into vector + std::vector args; + args.reserve(argc); + for (int i = 0; i < argc; ++i) + { + args.emplace_back(argv[i]); + } + + std::vector> server_list; + std::uint8_t protocol_version = 1; + + // Parse command line arguments + for (size_t i = 1; i < args.size(); ++i) + { + if (args[i] == "-h" || args[i] == "--help") + { + print_usage(args[0]); + return 0; + } + else if (args[i] == "--protocol-version") + { + if (i + 1 < args.size()) + { + protocol_version = static_cast(std::stoi(args[i + 1])); + ++i; + } + else + { + print_usage(args[0]); + return 1; + } + } + else + { + try + { + server_list.push_back(parse_server(args[i])); + } + catch (const std::exception& e) + { + std::cerr << e.what() << std::endl; + print_usage(args[0]); + return 1; + } + } + } + + // Fail if the server list is empty + if (server_list.empty()) + { + print_usage(args[0]); + return 1; + } + + // Create an io_context + auto io_context = std::make_shared(); + + // Create a client manager + auto client_manager = eCAL::service::ClientManager::create(io_context, eCAL::service::default_logger("Client", eCAL::service::LogLevel::DebugVerbose)); + + // Create and start an io_context thread. + // The io_context will be stopped, when the server_manager and client_manager are stopped. + std::thread io_context_thread([&io_context]() { io_context->run(); }); + + // Client Callback + // + // This callback will be called, when the service call is finished. + auto client_response_callback + = [](const eCAL::service::Error& error, const std::shared_ptr& response) -> void + { + if (error) + std::cerr << "Error calling service: " << error.ToString() << std::endl; + else + std::cout << "Received response: " << *response << std::endl; + }; + + // Event callbacks (empty) + auto client_event_callback = [](eCAL::service::ClientEventType /*event*/, const std::string& /*message*/) {}; + + // Create client + // The client will connect to the server on the given port. + auto client = client_manager->create_client(protocol_version, server_list, client_event_callback); + + // Call the service non-blocking. The response will be passed to the callback. + int counter = 1; + while(true) + { + const auto request = std::make_shared("Hello World " + std::to_string(counter)); + client->async_call_service(request, client_response_callback); + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + ++counter; + } + + std::cout << "Shutting down :)" << std::endl; + + // Use managers to stop servers and clients + client_manager->stop(); + + // Join the io_context thread + io_context_thread.join(); + +} diff --git a/ecal/service/samples/sample_server/CMakeLists.txt b/ecal/service/samples/sample_server/CMakeLists.txt new file mode 100644 index 0000000000..ffeca863a7 --- /dev/null +++ b/ecal/service/samples/sample_server/CMakeLists.txt @@ -0,0 +1,39 @@ +# ========================= eCAL LICENSE ================================= +# +# Copyright (C) 2016 - 2023 Continental Corporation +# +# 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. +# +# ========================= eCAL LICENSE ================================= + +project(service_sample_server) + +find_package(Threads REQUIRED) + +set(sources + src/main.cpp +) + +add_executable(${PROJECT_NAME} ${sources}) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + ecal_service) + +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_14) + +set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER core/service) + +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES + ${sources} +) diff --git a/ecal/service/samples/sample_server/src/main.cpp b/ecal/service/samples/sample_server/src/main.cpp new file mode 100644 index 0000000000..d1a7921d6b --- /dev/null +++ b/ecal/service/samples/sample_server/src/main.cpp @@ -0,0 +1,124 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2023 Continental Corporation + * + * 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. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +void print_usage(const std::string& arg0) +{ + std::cout << "Usage: " << arg0 << " [--port ] [--protocol-version ]" << std::endl; +} + +int main(int argc, char** argv) +{ + uint16_t port = 0; + std::uint8_t protocol_version = 1; + + // convert command line arguments into vector + std::vector args; + args.reserve(argc); + for (int i = 0; i < argc; ++i) + { + args.emplace_back(argv[i]); + } + + // parse command line arguments + for (size_t i = 1; i < args.size(); ++i) + { + if (args[i] == "-h" || args[i] == "--help") + { + print_usage(args[0]); + return 0; + } + else if (args[i] == "--port") + { + if (i + 1 < args.size()) + { + port = static_cast(std::stoi(args[i + 1])); + ++i; + } + else + { + print_usage(args[0]); + return 1; + } + } + else if (args[i] == "--protocol-version") + { + if (i + 1 < args.size()) + { + protocol_version = static_cast(std::stoi(args[i + 1])); + ++i; + } + else + { + print_usage(args[0]); + return 1; + } + } + else + { + print_usage(args[0]); + return 1; + } + } + + // Create an io_context + auto io_context = std::make_shared(); + + // Create a server manager + auto server_manager = eCAL::service::ServerManager::create(io_context, eCAL::service::default_logger("Server", eCAL::service::LogLevel::DebugVerbose)); + + // Server Service callback + // + // This callback will be called, when a client calls the service. + // It is responsible for filling the response object. + auto server_service_callback + = [](const std::shared_ptr& request, const std::shared_ptr& response) -> void + { + *response = "Response on \"" + *request + "\""; + }; + + // Event callbacks (empty) + auto server_event_callback = [](eCAL::service::ServerEventType /*event*/, const std::string& /*message*/) {}; + + // Create server + // If the port is 0, the server will choose a port automatically + auto server = server_manager->create_server(protocol_version, port, server_service_callback, true, server_event_callback); + + // Print server port + std::cout << "Started Service Server on port: " << server->get_port() << std::endl; + std::cout << "Using protocol version: " << std::to_string(protocol_version) << std::endl; + + // Start io_context in main thread + io_context->run(); + + // Use managers to stop server + server_manager->stop(); +} diff --git a/ecal/service/samples/sample_standalone/CMakeLists.txt b/ecal/service/samples/sample_standalone/CMakeLists.txt new file mode 100644 index 0000000000..0806b506a7 --- /dev/null +++ b/ecal/service/samples/sample_standalone/CMakeLists.txt @@ -0,0 +1,39 @@ +# ========================= eCAL LICENSE ================================= +# +# Copyright (C) 2016 - 2023 Continental Corporation +# +# 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. +# +# ========================= eCAL LICENSE ================================= + +project(service_sample_standalone) + +find_package(Threads REQUIRED) + +set(sources + src/main.cpp +) + +add_executable(${PROJECT_NAME} ${sources}) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + ecal_service) + +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_14) + +set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER core/service) + +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES + ${sources} +) diff --git a/ecal/service/sample/src/main.cpp b/ecal/service/samples/sample_standalone/src/main.cpp similarity index 91% rename from ecal/service/sample/src/main.cpp rename to ecal/service/samples/sample_standalone/src/main.cpp index be38fbca0f..482aaf4bce 100644 --- a/ecal/service/sample/src/main.cpp +++ b/ecal/service/samples/sample_standalone/src/main.cpp @@ -20,9 +20,17 @@ #include #include +#include +#include +#include + +#include #include +#include +#include #include -#include + +#include int main(int /*argc*/, char** /*argv*/) { @@ -69,7 +77,7 @@ int main(int /*argc*/, char** /*argv*/) // Create client // The client will connect to the server on the given port. - auto client = client_manager->create_client(1, "127.0.0.1", server->get_port(), client_event_callback); + auto client = client_manager->create_client(1, {{ "127.0.0.1", server->get_port() }}, client_event_callback); // Call the service non-blocking. The response will be passed to the callback. for (int i = 1; i <= 10; i++) diff --git a/ecal/service/test/src/ecal_tcp_service_test.cpp b/ecal/service/test/src/ecal_tcp_service_test.cpp index 67455b54d4..cd5abf2fcb 100644 --- a/ecal/service/test/src/ecal_tcp_service_test.cpp +++ b/ecal/service/test/src/ecal_tcp_service_test.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include // Should not be needed, when I use the server manager / client manager #include // Should not be needed, when I use the server manager / client manager @@ -59,8 +60,6 @@ eCAL::service::LoggerT critical_logger(const std::string& node_name) constexpr std::uint8_t min_protocol_version = 0; constexpr std::uint8_t max_protocol_version = 1; - - #if 1 TEST(ecal_service, RAII_TcpServiceServer) // NOLINT { @@ -135,7 +134,7 @@ TEST(ecal_service, RAII_TcpServiceClient) // NOLINT io_context->run(); }); - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, "127.0.0.1", 12345, client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", 12345 }}, client_event_callback); io_context->stop(); io_thread->join(); @@ -201,7 +200,7 @@ TEST(ecal_service, RAII_TcpServiceServerAndClient) // NOLINT EXPECT_EQ(tcp_server->get_connection_count(), 0); - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", tcp_server->get_port(), client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", tcp_server->get_port() }}, client_event_callback); tcp_client_weak = client_v1; client_v1->async_call_service(std::make_shared("Hello World"), client_slow_response_callback); @@ -283,7 +282,7 @@ TEST(ecal_service, RAII_StopDuringServiceCall) // NOLINT io_context->run(); }); - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", tcp_server->get_port(), client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", tcp_server->get_port() }}, client_event_callback); tcp_client_weak = client_v1; client_v1->async_call_service(std::make_shared("Hello World"), client_slow_response_callback); @@ -359,7 +358,7 @@ TEST(ecal_service, Communication_SlowCommunication) // NOLINT EXPECT_EQ(server->get_connection_count(), 0); } - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -470,7 +469,7 @@ TEST(ecal_service, CallbacksConnectDisconnect_ClientDisconnectsFirst) // NOLINT }; auto server = eCAL::service::Server::create(io_context, protocol_version, 0, server_service_callback, true, server_event_callback); - auto client = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback); + auto client = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -604,7 +603,7 @@ TEST(ecal_service, CommunicationAndCallbacks_ClientsDisconnectFirst) // NOLINT EXPECT_EQ(server->get_connection_count(), 0); } - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -777,7 +776,7 @@ TEST(ecal_service, CommunicationAndCallbacks_ServerDisconnectsFirst) // NOLINT }; auto server = eCAL::service::Server::create(io_context, protocol_version, 0, server_service_callback, true, server_event_callback); - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -946,7 +945,7 @@ TEST(ecal_service, CommunicationAndCallbacks_StressfulCommunication) // NOLINT num_client_event_callback_called++; }; - client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback, critical_logger("Client " + std::to_string(c)))); + client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback, critical_logger("Client " + std::to_string(c)))); } // Directly run a bunch of clients and call each client a bunch of times @@ -1087,7 +1086,7 @@ TEST(ecal_service, CommunicationAndCallbacks_StressfulCommunicationNoParallelCal num_clients_connected++; } }; - client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback, critical_logger("Client " + std::to_string(c)))); + client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback, critical_logger("Client " + std::to_string(c)))); } // wait for the clients to connect @@ -1234,7 +1233,7 @@ TEST(ecal_service, CommunicationAndCallbacks_StressfulCommunicationMassivePayloa num_client_event_callback_called++; }; - client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback, critical_logger("Client " + std::to_string(c)))); + client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback, critical_logger("Client " + std::to_string(c)))); } // Directly run a bunch of clients and call each client a bunch of times @@ -1351,10 +1350,10 @@ TEST(ecal_service, Callback_ServerAndClientManagers) // NOLINT auto server1 = server_manager->create_server(protocol_version, 0, server_service_callback, true, increment_atomic_signalable(server1_event_callback_called)); auto server2 = server_manager->create_server(protocol_version, 0, server_service_callback, true, increment_atomic_signalable(server2_event_callback_called)); - auto client1_1 = client_manager->create_client(protocol_version, "127.0.0.1", server1->get_port(), increment_atomic_signalable(client1_1_event_callback_called)); - auto client1_2 = client_manager->create_client(protocol_version, "127.0.0.1", server1->get_port(), increment_atomic_signalable(client1_2_event_callback_called)); - auto client2_1 = client_manager->create_client(protocol_version, "127.0.0.1", server2->get_port(), increment_atomic_signalable(client2_1_event_callback_called)); - auto client2_2 = client_manager->create_client(protocol_version, "127.0.0.1", server2->get_port(), increment_atomic_signalable(client2_2_event_callback_called)); + auto client1_1 = client_manager->create_client(protocol_version, {{ "127.0.0.1", server1->get_port() }}, increment_atomic_signalable(client1_1_event_callback_called)); + auto client1_2 = client_manager->create_client(protocol_version, {{ "127.0.0.1", server1->get_port() }}, increment_atomic_signalable(client1_2_event_callback_called)); + auto client2_1 = client_manager->create_client(protocol_version, {{ "127.0.0.1", server2->get_port() }}, increment_atomic_signalable(client2_1_event_callback_called)); + auto client2_2 = client_manager->create_client(protocol_version, {{ "127.0.0.1", server2->get_port() }}, increment_atomic_signalable(client2_2_event_callback_called)); // Wait for the clients to be connected client1_1_event_callback_called.wait_for([&](int value) { return value >= 1; }, std::chrono::seconds(5)); @@ -1474,7 +1473,7 @@ TEST(ecal_service, Callback_ServiceCallFromCallback) // NOLINT EXPECT_EQ(num_client_response_callback1_called, 0); EXPECT_EQ(num_client_response_callback2_called, 0); - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); const eCAL::service::ClientSession::ResponseCallbackT response_callback = [&num_client_response_callback1_called, &num_client_response_callback2_called, client_v1] @@ -1565,7 +1564,7 @@ TEST(ecal_service, Callback_SerializedServiceCallbacks) // NOLINT clients.reserve(num_clients); for (int i = 0; i < num_clients; i++) { - clients.push_back(client_manager->create_client(protocol_version, "127.0.0.1", server->get_port(), client_event_callback)); + clients.push_back(client_manager->create_client(protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback)); } num_client_event_callback_called.wait_for([&num_clients](int value) -> bool { return value >= num_clients; }, std::chrono::milliseconds(500)); @@ -1607,7 +1606,7 @@ TEST(ecal_service, Callback_SerializedServiceCallbacks) // NOLINT #if 1 // Call different eCAL Service API functions from within the callbacks -TEST(ecal_service, Callback_ApiCallsFromCallbacks) +TEST(ecal_service, Callback_ApiCallsFromCallbacks) // NOLINT { for (std::uint8_t protocol_version = min_protocol_version; protocol_version <= max_protocol_version; protocol_version++) { @@ -1670,8 +1669,9 @@ TEST(ecal_service, Callback_ApiCallsFromCallbacks) if(client) { // We just test if those functions can be called without crashing - auto address = client->get_address(); + auto address = client->get_host(); auto port = client->get_port(); + auto endpoint = client->get_remote_endpoint(); auto protocol_version = client->get_accepted_protocol_version(); auto queue_size = client->get_queue_size(); auto state = client->get_state(); @@ -1687,8 +1687,9 @@ TEST(ecal_service, Callback_ApiCallsFromCallbacks) if (client) { // We just test if those functions can be called without crashing - auto address = client->get_address(); + auto address = client->get_host(); auto port = client->get_port(); + auto endpoint = client->get_remote_endpoint(); auto protocol_version = client->get_accepted_protocol_version(); auto queue_size = client->get_queue_size(); auto state = client->get_state(); @@ -1704,7 +1705,7 @@ TEST(ecal_service, Callback_ApiCallsFromCallbacks) EXPECT_EQ(num_client_response_callback_called.get(), 0); EXPECT_EQ(num_client_event_callback_called.get(), 0); - client = eCAL::service::ClientSession::create(io_context, protocol_version, "127.0.0.1", server->get_port(), client_event_callback); + client = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -1750,6 +1751,126 @@ TEST(ecal_service, Callback_ApiCallsFromCallbacks) } #endif +#if 1 +// Connect to a list of hosts of which the first one does not exist, so the next one is used and connected to +TEST(ecal_service, BackupHost) +{ + for (std::uint8_t protocol_version = min_protocol_version; protocol_version <= max_protocol_version; protocol_version++) + { + const auto io_context = std::make_shared(); + const asio::io_context::work dummy_work(*io_context); + + atomic_signalable num_server_service_callback_called(0); + atomic_signalable num_server_event_callback_called (0); + atomic_signalable num_client_response_callback_called(0); + atomic_signalable num_client_event_callback_called (0); + + const eCAL::service::Server::ServiceCallbackT server_service_callback + = [&num_server_service_callback_called](const std::shared_ptr& /*request*/, const std::shared_ptr& /*response*/) -> void + { + num_server_service_callback_called++; + }; + + const eCAL::service::Server::EventCallbackT server_event_callback + = [&num_server_event_callback_called](eCAL::service::ServerEventType event, const std::string& /*message*/) -> void + { + num_server_event_callback_called++; + }; + + const eCAL::service::ClientSession::EventCallbackT client_event_callback + = [&num_client_event_callback_called](eCAL::service::ClientEventType event, const std::string& /*message*/) -> void + { + num_client_event_callback_called++; + }; + + auto server = eCAL::service::Server::create(io_context, protocol_version, 0, server_service_callback, true, server_event_callback); + + EXPECT_EQ(num_server_service_callback_called, 0); + EXPECT_EQ(num_server_event_callback_called, 0); + EXPECT_EQ(num_client_response_callback_called, 0); + EXPECT_EQ(num_client_event_callback_called, 0); + + std::vector> server_list = + { + { "NonExistingEndpoint", 123 }, // This endpoint cannot be resolved + { "127.0.0.1", 123 }, // This endpoint can be resolved, but the port is wrong + { "127.0.0.1", server->get_port() } // This endpoint is the correct one and will be tried last + }; + + auto client = eCAL::service::ClientSession::create(io_context, protocol_version, server_list, client_event_callback); + + std::thread io_thread([&io_context]() + { + io_context->run(); + }); + + // Wait for the connected events to be called + num_server_event_callback_called.wait_for([](int value) { return value >= 1; }, std::chrono::milliseconds(10000)); // Going through the wrong endpoints may take some time + num_client_event_callback_called.wait_for([](int value) { return value >= 1; }, std::chrono::milliseconds(10000)); // Going through the wrong endpoints may take some time + + EXPECT_EQ(num_server_service_callback_called, 0); + EXPECT_EQ(num_server_event_callback_called, 1); + EXPECT_EQ(num_client_response_callback_called, 0); + EXPECT_EQ(num_client_event_callback_called, 1); + + // Check what host the client is connected to + auto connected_host = client->get_host(); + auto connected_port = client->get_port(); + auto endpoint = client->get_remote_endpoint(); + EXPECT_EQ(connected_host, "127.0.0.1"); + EXPECT_EQ(connected_port, server->get_port()); + EXPECT_EQ(endpoint.port(), server->get_port()); + EXPECT_EQ(endpoint.address().to_string(), "127.0.0.1"); + + // Call service and wait for the response + const eCAL::service::ClientSession::ResponseCallbackT response_callback + = [&num_client_response_callback_called](const eCAL::service::Error& error, const std::shared_ptr& /*response*/) -> void + { + EXPECT_FALSE(bool(error)); + num_client_response_callback_called++; + }; + + client->async_call_service(std::make_shared("Hello World"), response_callback); + + num_client_response_callback_called.wait_for([](int value) { return value >= 1; }, std::chrono::milliseconds(500)); + + EXPECT_EQ(num_server_service_callback_called, 1); + EXPECT_EQ(num_server_event_callback_called, 1); + EXPECT_EQ(num_client_response_callback_called, 1); + EXPECT_EQ(num_client_event_callback_called, 1); + + // Shutdown + server = nullptr; + client = nullptr; + + // join the io_thread + io_context->stop(); + io_thread.join(); + } +} +#endif + +#if 1 +// Test that the client create throws an exception when bein created with an empty server list +TEST(ecal_service, EmptyServerList) +{ + // Regular client + for (std::uint8_t protocol_version = min_protocol_version; protocol_version <= max_protocol_version; protocol_version++) + { + const auto io_context = std::make_shared(); + EXPECT_THROW(eCAL::service::ClientSession::create(io_context, protocol_version, {}, [](eCAL::service::ClientEventType, const std::string&) -> void {}), std::invalid_argument); + } + + // Client manager + for (std::uint8_t protocol_version = min_protocol_version; protocol_version <= max_protocol_version; protocol_version++) + { + const auto io_context = std::make_shared(); + auto client_manager = eCAL::service::ClientManager::create(io_context); + EXPECT_THROW(client_manager->create_client(protocol_version, {}, [](eCAL::service::ClientEventType, const std::string&) -> void {}), std::invalid_argument); + } +} +#endif + #if 1 TEST(ecal_service, ErrorCallback_ErrorCallbackNoServer) // NOLINT { @@ -1779,7 +1900,7 @@ TEST(ecal_service, ErrorCallback_ErrorCallbackNoServer) // NOLINT EXPECT_EQ(num_client_response_callback_called, 0); - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, "NonExistingEndpoint", 12345, client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "NonExistingEndpoint", 12345 }}, client_event_callback); // Run the io_service std::thread io_thread([&io_context]() @@ -1854,7 +1975,7 @@ TEST(ecal_service, ErrorCallback_ErrorCallbackServerHasDisconnected) // NOLINT }; auto server = eCAL::service::Server::create(io_context, protocol_version, 0, server_service_callback, true, server_event_callback); - auto client = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback); + auto client = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -2001,7 +2122,7 @@ TEST(ecal_service, ErrorCallback_ErrorCallbackClientDisconnects) // NOLINT {}; auto server = eCAL::service::Server::create(io_context, protocol_version, 0, server_service_callback, true, server_event_callback); - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -2156,7 +2277,7 @@ TEST(ecal_service, ErrorCallback_StressfulErrorsHalfwayThrough) // NOLINT num_client_event_callback_called++; }; - client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback, critical_logger("Client " + std::to_string(c)))); + client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback, critical_logger("Client " + std::to_string(c)))); } // Directly run a bunch of clients and call each client a bunch of times @@ -2340,7 +2461,7 @@ TEST(ecal_service, ErrorCallback_StressfulErrorsHalfwayThroughWithManagers) // N num_client_event_callback_called++; }; - client_list.push_back(client_manager->create_client(protocol_version,"127.0.0.1", server->get_port(), client_event_callback)); + client_list.push_back(client_manager->create_client(protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback)); } // Directly run a bunch of clients and call each client a bunch of times @@ -2466,7 +2587,7 @@ TEST(ecal_service, BlockingCall_RegularBlockingCall) // NOLINT {}; auto server = eCAL::service::Server::create(io_context, protocol_version, 0, server_service_callback, true, server_event_callback); - auto client = eCAL::service::ClientSession::create(io_context, protocol_version, "127.0.0.1", server->get_port(), client_event_callback); + auto client = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -2538,7 +2659,7 @@ TEST(ecal_service, BlockingCall_BlockingCallWithErrorHalfwayThrough) // NOLINT {}; auto server = eCAL::service::Server::create(io_context, protocol_version, 0, server_service_callback, true, server_event_callback); - auto client = eCAL::service::ClientSession::create(io_context, protocol_version, "127.0.0.1", server->get_port(), client_event_callback); + auto client = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -2671,7 +2792,7 @@ TEST(ecal_service, BlockingCall_Stopped) // NOLINT // This test shows the prope {}; auto server = server_manager->create_server(protocol_version, 0, server_service_callback, true, server_event_callback); - auto client = client_manager->create_client(protocol_version, "127.0.0.1", server->get_port(), client_event_callback); + auto client = client_manager->create_client(protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -2739,4 +2860,4 @@ TEST(ecal_service, BlockingCall_Stopped) // NOLINT // This test shows the prope } } } -#endif \ No newline at end of file +#endif