Skip to content

Commit

Permalink
Support building with boost asio (#247)
Browse files Browse the repository at this point in the history
### Public-Facing Changes

Support building with boost asio

### Description
Avoids a hard dependency on standalone asio and provides users with more
flexibility when using foxglove_bridge in a bigger project. Related:
foxglove/ws-protocol#483

Successfully built and tested melodic & noetic with boost asio. I think
it would make sense to add a test for this as well.
  • Loading branch information
achim-k authored Jul 11, 2023
1 parent 3933554 commit c3236d5
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 83 deletions.
21 changes: 13 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,22 @@ jobs:
strategy:
fail-fast: false
matrix:
include:
- ros_distribution: melodic
- ros_distribution: noetic
- ros_distribution: galactic
- ros_distribution: humble
- ros_distribution: iron
- ros_distribution: rolling
ros_distribution: [melodic, noetic, galactic, humble, iron, rolling]

name: Test (ROS ${{ matrix.ros_distribution }})
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- run: make ${{ matrix.ros_distribution }}-test

test-boost-asio:
strategy:
fail-fast: false
matrix:
ros_distribution: [noetic, humble, rolling]

name: Test (ROS ${{ matrix.ros_distribution }}, Boost Asio)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: make ${{ matrix.ros_distribution }}-test-boost-asio
18 changes: 14 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ project(foxglove_bridge LANGUAGES CXX VERSION 0.6.4)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_definitions(-DASIO_STANDALONE)

macro(enable_strict_compiler_warnings target)
if (MSVC)
Expand All @@ -35,6 +34,16 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()

# Determine wheter to use standalone or boost asio
option(USE_ASIO_STANDALONE "Build with standalone ASIO" ON)
if(USE_ASIO_STANDALONE)
message(STATUS "Using standalone ASIO")
add_definitions(-DASIO_STANDALONE)
else()
message(STATUS "Using Boost ASIO")
find_package(Boost REQUIRED)
endif(USE_ASIO_STANDALONE)

# Detect big-endian architectures
include(TestBigEndian)
TEST_BIG_ENDIAN(ENDIAN)
Expand Down Expand Up @@ -179,17 +188,18 @@ if(ROS_BUILD_TYPE STREQUAL "catkin")
find_package(GTest REQUIRED)
endif()
find_package(rostest REQUIRED)
find_package(Boost REQUIRED COMPONENTS system)

catkin_add_gtest(version_test foxglove_bridge_base/tests/version_test.cpp)
target_link_libraries(version_test foxglove_bridge_base)
target_link_libraries(version_test foxglove_bridge_base ${Boost_LIBRARIES})
enable_strict_compiler_warnings(version_test)

catkin_add_gtest(serialization_test foxglove_bridge_base/tests/serialization_test.cpp)
target_link_libraries(serialization_test foxglove_bridge_base)
target_link_libraries(serialization_test foxglove_bridge_base ${Boost_LIBRARIES})
enable_strict_compiler_warnings(foxglove_bridge)

catkin_add_gtest(base64_test foxglove_bridge_base/tests/base64_test.cpp)
target_link_libraries(base64_test foxglove_bridge_base)
target_link_libraries(base64_test foxglove_bridge_base ${Boost_LIBRARIES})
enable_strict_compiler_warnings(foxglove_bridge)

add_rostest_gtest(smoke_test ros1_foxglove_bridge/tests/smoke.test ros1_foxglove_bridge/tests/smoke_test.cpp)
Expand Down
4 changes: 3 additions & 1 deletion Dockerfile.ros1
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ COPY foxglove_bridge_base src/ros-foxglove-bridge/foxglove_bridge_base
COPY nodelets.xml src/ros-foxglove-bridge/nodelets.xml
COPY ros1_foxglove_bridge src/ros-foxglove-bridge/ros1_foxglove_bridge

ARG USE_ASIO_STANDALONE=ON

# Build the Catkin workspace
RUN . /opt/ros/$ROS_DISTRO/setup.sh \
&& catkin_make
&& catkin_make -DUSE_ASIO_STANDALONE=$USE_ASIO_STANDALONE

# source workspace from entrypoint
RUN sed --in-place \
Expand Down
5 changes: 4 additions & 1 deletion Dockerfile.ros2
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ WORKDIR $ROS_WS
RUN apt-get update && apt-get install -y --no-install-recommends \
nlohmann-json3-dev \
libasio-dev \
libboost-all-dev \
libssl-dev \
libwebsocketpp-dev \
&& rm -rf /var/lib/apt/lists/*
Expand All @@ -37,9 +38,11 @@ COPY CMakeLists.txt src/ros-foxglove-bridge/CMakeLists.txt
COPY foxglove_bridge_base src/ros-foxglove-bridge/foxglove_bridge_base
COPY ros2_foxglove_bridge src/ros-foxglove-bridge/ros2_foxglove_bridge

ARG USE_ASIO_STANDALONE=ON

# Build the ROS 2 workspace
RUN . /opt/ros/$ROS_DISTRO/setup.sh \
&& colcon build --event-handlers console_direct+
&& colcon build --event-handlers console_direct+ --cmake-args -DUSE_ASIO_STANDALONE=$USE_ASIO_STANDALONE

# source workspace from entrypoint
RUN sed --in-place \
Expand Down
101 changes: 44 additions & 57 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
ROS1_DISTRIBUTIONS := melodic noetic
ROS2_DISTRIBUTIONS := galactic humble iron rolling

define generate_ros1_targets
.PHONY: $(1)
$(1):
docker build -t foxglove_bridge_$(1) --pull -f Dockerfile.ros1 --build-arg ROS_DISTRIBUTION=$(1) .

.PHONY: $(1)-test
$(1)-test: $(1)
docker run -t --rm foxglove_bridge_$(1) bash -c "catkin_make run_tests && catkin_test_results"

.PHONY: $(1)-boost-asio
$(1)-boost-asio:
docker build -t foxglove_bridge_$(1)_boost_asio --pull -f Dockerfile.ros1 --build-arg ROS_DISTRIBUTION=$(1) --build-arg USE_ASIO_STANDALONE=OFF .

.PHONY: $(1)-test-boost-asio
$(1)-test-boost-asio: $(1)-boost-asio
docker run -t --rm foxglove_bridge_$(1)_boost_asio bash -c "catkin_make run_tests && catkin_test_results"
endef

define generate_ros2_targets
.PHONY: $(1)
$(1):
docker build -t foxglove_bridge_$(1) --pull -f Dockerfile.ros2 --build-arg ROS_DISTRIBUTION=$(1) .

.PHONY: $(1)-test
$(1)-test: $(1)
docker run -t --rm foxglove_bridge_$(1) colcon test --event-handlers console_cohesion+ --return-code-on-test-failure

.PHONY: $(1)-boost-asio
$(1)-boost-asio:
docker build -t foxglove_bridge_$(1)-boost-asio --pull -f Dockerfile.ros2 --build-arg ROS_DISTRIBUTION=$(1) --build-arg USE_ASIO_STANDALONE=OFF .

.PHONY: $(1)-test-boost-asio
$(1)-test-boost-asio: $(1)-boost-asio
docker run -t --rm foxglove_bridge_$(1)-boost-asio colcon test --event-handlers console_cohesion+ --return-code-on-test-failure
endef

$(foreach distribution,$(ROS1_DISTRIBUTIONS),$(eval $(call generate_ros1_targets,$(strip $(distribution)))))
$(foreach distribution,$(ROS2_DISTRIBUTIONS),$(eval $(call generate_ros2_targets,$(strip $(distribution)))))


default: ros2

.PHONY: ros1
Expand All @@ -8,68 +51,12 @@ ros1:
ros2:
docker build -t foxglove_bridge_ros2 --pull -f Dockerfile.ros2 .

.PHONY: melodic
melodic:
docker build -t foxglove_bridge_melodic --pull -f Dockerfile.ros1 --build-arg ROS_DISTRIBUTION=melodic .

.PHONY: noetic
noetic:
docker build -t foxglove_bridge_noetic --pull -f Dockerfile.ros1 --build-arg ROS_DISTRIBUTION=noetic .

.PHONY: galactic
galactic:
docker build -t foxglove_bridge_galactic --pull -f Dockerfile.ros2 --build-arg ROS_DISTRIBUTION=galactic .

.PHONY: humble
humble:
docker build -t foxglove_bridge_humble --pull -f Dockerfile.ros2 --build-arg ROS_DISTRIBUTION=humble .

.PHONY: iron
iron:
docker build -t foxglove_bridge_iron --pull -f Dockerfile.ros2 --build-arg ROS_DISTRIBUTION=iron .

.PHONY: rolling
rolling:
docker build -t foxglove_bridge_rolling --pull -f Dockerfile.ros2 --build-arg ROS_DISTRIBUTION=rolling .

.PHONY: rosdev
rosdev:
docker build -t foxglove_bridge_rosdev --pull -f .devcontainer/Dockerfile .

clean:
docker rmi -f foxglove_bridge_ros1
docker rmi -f foxglove_bridge_ros2
docker rmi -f foxglove_bridge_melodic
docker rmi -f foxglove_bridge_noetic
docker rmi -f foxglove_bridge_galactic
docker rmi -f foxglove_bridge_humble
docker rmi -f foxglove_bridge_iron
docker rmi -f foxglove_bridge_rolling
docker rmi -f foxglove_bridge_rosdev

.PHONY: melodic-test
melodic-test: melodic
docker run -t --rm foxglove_bridge_melodic bash -c "catkin_make run_tests && catkin_test_results"

.PHONY: noetic-test
noetic-test: noetic
docker run -t --rm foxglove_bridge_noetic bash -c "catkin_make run_tests && catkin_test_results"

.PHONY: galactic-test
galactic-test: galactic
docker run -t --rm foxglove_bridge_galactic colcon test --event-handlers console_cohesion+ --return-code-on-test-failure

.PHONY: humble-test
humble-test: humble
docker run -t --rm foxglove_bridge_humble colcon test --event-handlers console_cohesion+ --return-code-on-test-failure

.PHONY: iron-test
iron-test: iron
docker run -t --rm foxglove_bridge_iron colcon test --event-handlers console_cohesion+ --return-code-on-test-failure

.PHONY: rolling-test
rolling-test: rolling
docker run -t --rm foxglove_bridge_rolling colcon test --event-handlers console_cohesion+ --return-code-on-test-failure
docker rmi $(docker images --filter=reference="foxglove_bridge_*" -q)

.PHONY: lint
lint: rosdev
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#include <functional>

#include <asio/ip/address.hpp>
#include <websocketpp/common/asio.hpp>
#include <websocketpp/logger/levels.hpp>

#include "common.hpp"
Expand All @@ -11,7 +11,7 @@ namespace foxglove {

using LogCallback = std::function<void(WebSocketLogLevel, char const*)>;

inline std::string IPAddressToString(const asio::ip::address& addr) {
inline std::string IPAddressToString(const websocketpp::lib::asio::ip::address& addr) {
if (addr.is_v6()) {
return "[" + addr.to_string() + "]";
}
Expand Down
21 changes: 11 additions & 10 deletions foxglove_bridge_base/include/foxglove_bridge/websocket_server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ inline Server<ServerConfiguration>::Server(std::string name, LogCallback logger,
_server.get_alog().set_callback(_logger);
_server.get_elog().set_callback(_logger);

std::error_code ec;
websocketpp::lib::error_code ec;
_server.init_asio(ec);
if (ec) {
throw std::runtime_error("Failed to initialize websocket server: " + ec.message());
Expand Down Expand Up @@ -245,7 +245,7 @@ inline Server<ServerConfiguration>::~Server() {}

template <typename ServerConfiguration>
inline void Server<ServerConfiguration>::socketInit(ConnHandle hdl) {
std::error_code ec;
websocketpp::lib::asio::error_code ec;
_server.get_con_from_hdl(hdl)->get_raw_socket().set_option(Tcp::no_delay(true), ec);
if (ec) {
_server.get_elog().write(RECOVERABLE, "Failed to set TCP_NODELAY: " + ec.message());
Expand Down Expand Up @@ -394,7 +394,7 @@ inline void Server<ServerConfiguration>::stop() {
}

_server.get_alog().write(APP, "Stopping WebSocket server");
std::error_code ec;
websocketpp::lib::error_code ec;

_server.stop_perpetual();

Expand Down Expand Up @@ -471,7 +471,7 @@ inline void Server<ServerConfiguration>::start(const std::string& host, uint16_t
throw std::runtime_error("Server already started");
}

std::error_code ec;
websocketpp::lib::error_code ec;

_server.listen(host, std::to_string(port), ec);
if (ec) {
Expand All @@ -494,8 +494,9 @@ inline void Server<ServerConfiguration>::start(const std::string& host, uint16_t
throw std::runtime_error("WebSocket server failed to listen on port " + std::to_string(port));
}

auto endpoint = _server.get_local_endpoint(ec);
if (ec) {
websocketpp::lib::asio::error_code asioEc;
auto endpoint = _server.get_local_endpoint(asioEc);
if (asioEc) {
throw std::runtime_error("Failed to resolve the local endpoint: " + ec.message());
}

Expand Down Expand Up @@ -1182,7 +1183,7 @@ template <typename ServerConfiguration>
inline void Server<ServerConfiguration>::sendMessage(ConnHandle clientHandle, ChannelId chanId,
uint64_t timestamp, const uint8_t* payload,
size_t payloadSize) {
std::error_code ec;
websocketpp::lib::error_code ec;
const auto con = _server.get_con_from_hdl(clientHandle, ec);
if (ec || !con) {
return;
Expand Down Expand Up @@ -1252,7 +1253,7 @@ inline void Server<ServerConfiguration>::sendServiceResponse(ConnHandle clientHa

template <typename ServerConfiguration>
inline uint16_t Server<ServerConfiguration>::getPort() {
std::error_code ec;
websocketpp::lib::asio::error_code ec;
auto endpoint = _server.get_local_endpoint(ec);
if (ec) {
throw std::runtime_error("Server not listening on any port. Has it been started before?");
Expand Down Expand Up @@ -1341,7 +1342,7 @@ inline void Server<ServerConfiguration>::updateConnectionGraph(

template <typename ServerConfiguration>
inline std::string Server<ServerConfiguration>::remoteEndpointString(ConnHandle clientHandle) {
std::error_code ec;
websocketpp::lib::error_code ec;
const auto con = _server.get_con_from_hdl(clientHandle, ec);
return con ? con->get_remote_endpoint() : "(unknown)";
}
Expand Down Expand Up @@ -1393,7 +1394,7 @@ inline bool Server<ServerConfiguration>::hasCapability(const std::string& capabi
template <typename ServerConfiguration>
inline void Server<ServerConfiguration>::sendFetchAssetResponse(
ConnHandle clientHandle, const FetchAssetResponse& response) {
std::error_code ec;
websocketpp::lib::error_code ec;
const auto con = _server.get_con_from_hdl(clientHandle, ec);
if (ec || !con) {
return;
Expand Down

0 comments on commit c3236d5

Please sign in to comment.