Skip to content

Commit

Permalink
Support for ROS2 standalone (#79)
Browse files Browse the repository at this point in the history
* Add ROS2 standalone build option

* Separate ros2_standalone CMakeLists & add configurable rpath

* Polish build tools to compile extenstions easier

* Handle command nvcc not found

* Add setup script for windows

* Adjust README to setup_win script

* Restore support for cyclonedds

* Add documentation for ROS2 extension

* Change steps order in windows building section

* Add script for applying required changes in rclcpp

* Merge setup.bash and setup_win_py into setup.py

* Apply suggestions from code review

Co-authored-by: Piotr Rybicki <[email protected]>

* ROS2 standalone build - setup.py review suggestions (#89)

* Fix indentation in setup.py

* Fix indentation, introduce main()

* Refactor setup.py script

* Apply suggestions from code review

Co-authored-by: Mateusz Szczygielski <[email protected]>

* Inform about default --make and --ninja args

Co-authored-by: Mateusz Szczygielski <[email protected]>

* Fix CUDA_MIN_VER_PATCH for Windows

* Add $ORIGIN explanation

* Add comments to ros2_standalone CMakeLists

* Fix README building typos

* Fix regex for python libs on Windows

Co-authored-by: Piotr Rybicki <[email protected]>
  • Loading branch information
msz-rai and prybicki committed Mar 9, 2023
1 parent 69150fd commit 6fa2364
Show file tree
Hide file tree
Showing 11 changed files with 514 additions and 116 deletions.
14 changes: 12 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ set(RGL_BUILD_TESTS ON CACHE BOOL
set(RGL_BUILD_TOOLS ON CACHE BOOL "Enables building RGL executable tools")

# Extensions configuration
set(RGL_BUILD_ROS2_EXTENSION ON CACHE BOOL
set(RGL_BUILD_ROS2_EXTENSION OFF CACHE BOOL
"Enables building ROS2 extension. It requires installed and sourced ROS2.")
set(RGL_BUILD_ROS2_EXTENSION_STANDALONE OFF CACHE BOOL
"Enables building ROS2 extension in standalone mode. It requires installed and sourced ROS2.")

# Hide automatically generated CTest targets
set_property(GLOBAL PROPERTY CTEST_TARGETS_ADDED 1)
Expand Down Expand Up @@ -93,7 +95,10 @@ add_library(RobotecGPULidar SHARED

set_property(TARGET RobotecGPULidar PROPERTY POSITION_INDEPENDENT_CODE ON)

if (RGL_BUILD_ROS2_EXTENSION)
if (RGL_BUILD_ROS2_EXTENSION OR RGL_BUILD_ROS2_EXTENSION_STANDALONE)
if (NOT $ENV{ROS_DISTRO} STREQUAL "humble")
message(FATAL_ERROR "ROS $ENV{ROS_DISTRO} not supported. Only humble is available.")
endif()
find_package(rclcpp REQUIRED)
find_package(sensor_msgs REQUIRED)
target_sources(RobotecGPULidar PRIVATE src/api/apiRos2.cpp src/graph/Ros2PublishPointsNode.cpp)
Expand Down Expand Up @@ -165,3 +170,8 @@ endif()
if (RGL_BUILD_TOOLS)
add_subdirectory(tools)
endif()

# Include ros2_standalone
if(RGL_BUILD_ROS2_EXTENSION_STANDALONE)
add_subdirectory(ros2_standalone)
endif()
6 changes: 3 additions & 3 deletions DockerfileLatest
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ RUN apt install -y \
python3

# Install RGL dependencies via vcpkg
COPY setup.bash /
RUN /setup.bash
RUN rm /setup.bash
COPY setup.py /
RUN /setup.py --install-deps
RUN rm /setup.py

WORKDIR /code

Expand Down
6 changes: 3 additions & 3 deletions DockerfileMinimal
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ RUN apt install -y \
python3

# Install RGL dependencies via vcpkg
COPY setup.bash /
RUN /setup.bash
RUN rm /setup.bash
COPY setup.py /
RUN /setup.py --install-deps
RUN rm /setup.py

WORKDIR /code

Expand Down
44 changes: 23 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ And more:

An introduction to the RGL API along with an example can be found [here](docs/Usage.md).

## Extensions

`RobotecGPULidar` library can be built with extensions enhancing RGL with additional functions:
- `ROS2` - adds a node to publish point cloud messages to [ROS2](https://www.ros.org/). Check [ROS2 extension doc](docs/Ros2Extension.md) for more information, build instructions, and usage.

## Building in Docker (Linux)

Two dockerfiles are prepared:
Expand All @@ -61,7 +66,7 @@ Build instructions:
3. `export OptiX_INSTALL_DIR=<Path to OptiX>`
4. `docker build . -f DockerfileMinimal --tag rgl:minimal`
5. `docker run --net=host --gpus all -v $(pwd):/code -v ${OptiX_INSTALL_DIR}:/optix -e OptiX_INSTALL_DIR=/optix -e NVIDIA_DRIVER_CAPABILITIES=all -it rgl:minimal /bin/bash`
6. `./setup.bash --cmake --make -j`
6. `./setup.py --make="-j"`

## Building on Ubuntu

Expand All @@ -70,32 +75,29 @@ Build instructions:
1. You may be asked to create Nvidia account to download
3. Export environment variable:
1. `export OptiX_INSTALL_DIR=<your-OptiX-path>`.
4. Use `setup.bash --cmake --make` script.
4. Run `./setup.py --install-deps` to install RGL dependencies.
- It will install dependencies from `apt` and [vcpkg](https://vcpkg.io/en/index.html).
- It will run CMake and then make.
5. Use `setup.py` script to build.
- It will use CMake to generate files for build system (make) and build.
- You can pass optional CMake and make parameters, e.g.
- `./setup.bash --cmake -DCMAKE_BUILD_TYPE=Debug --make -j 16`
- `./setup.py --cmake="-DCMAKE_BUILD_TYPE=Debug" --make="-j 16"`
- See `./setup.py --help` for usage information.

## Building on Windows

1. Install [CUDA Toolkit](https://developer.nvidia.com/cuda-downloads) **11.4.4+**.
2. Download [NVidia OptiX](https://developer.nvidia.com/designworks/optix/downloads/legacy) **7.2**.
1. Install [Microsoft Visual Studio](https://visualstudio.microsoft.com/pl/downloads/) (Visual Studio 2019 when using ROS2 extension) with **C++ CMake tools for Windows** component.
2. Install [CUDA Toolkit](https://developer.nvidia.com/cuda-downloads) **11.4.4+**.
3. Download [NVidia OptiX](https://developer.nvidia.com/designworks/optix/downloads/legacy) **7.2**.
- use the default location or set environment variable `OptiX_INSTALL_DIR`
3. Install [PCL](https://pointclouds.org/) 1.12:
1. Get [vcpkg](https://vcpkg.io/en/index.html):\
`git clone -b 2022.08.15 --single-branch --depth 1 https://github.com/microsoft/vcpkg`
2. Bootstrap `vcpkg`:\
`.\vcpkg\bootstrap-vcpkg.bat`
3. Install PCL:\
`.\vcpkg\vcpkg.exe install pcl[core,visualization]:x64-windows`
4. In order to use vcpkg with Visual Studio, run the following command (may require administrator elevation):\
`.\vcpkg\vcpkg.exe integrate install`
5. In order to use vcpkg with CMake, you can use the toolchain file:\
`cmake -B [build directory] -S . "-DCMAKE_TOOLCHAIN_FILE=[path to vcpkg]/scripts/buildsystems/vcpkg.cmake"`\
`cmake --build [build directory]`
4. Build the project:
- You can use [CLion IDE](https://www.jetbrains.com/clion/) (tested)
- Alternatively - [cmake-gui](https://cmake.org/download/) and Microsoft Visual Studio
4. Install [Python3](https://www.python.org/downloads/).
5. Run `x64 Native Tools Command Prompt for VS 20xx` and navigate to RGL repository.
6. Run `python setup.py --install-deps` command to install RGL dependencies.
- It will install dependencies from [vcpkg](https://vcpkg.io/en/index.html).
7. Run `python setup.py` command to build the project.
- It will use CMake to generate files for build system (ninja) and build.
- You can pass optional CMake and ninja parameters, e.g.
- `python setup.py --cmake="-DCMAKE_BUILD_TYPE=Debug" --ninja="-j 16"`
- See `python setup.py --help` for usage information.

## Acknowledgements

Expand Down
112 changes: 112 additions & 0 deletions docs/Ros2Extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# RGL ROS2 extension

The extension introduces the node to publish [PointCloud2](https://docs.ros2.org/foxy/api/sensor_msgs/msg/PointCloud2.html) messages to ROS2. RGL creates a ROS2 node named `RobotecGPULidar` and registers publishers based on constructed RGL nodes.

Supported ROS2 distributions:
- Humble

Supported DDS implementations for ROS2 standalone build:
- Eclipse Cyclone DDS
- eProsima Fast DDS

## Building

RGL ROS2 extension can be built in two flavors:

- **standalone** - ROS2 installation is not required on the target machine. RGL build will include all required ROS2 dependencies.
- **overlay** - Assumes existence of supported ROS2 installation on the target machine. RGL will try to use the existing installation of ROS2 dependencies.


### Ubuntu 22

#### Prerequisites

- Requirements listed in the main [README](../README.md) (section `Building on Ubuntu`).
- ROS2 installed on the system and sourced.
- For standalone build:
- ROS2 `cyclonedds` and `fastrtps` packages.
- `patchelf` tool.
```bash
apt install -y ros-${ROS_DISTRO}-cyclonedds ros-${ROS_DISTRO}-rmw-cyclonedds-cpp
apt install -y ros-${ROS_DISTRO}-fastrtps ros-${ROS_DISTRO}-rmw-fastrtps-cpp
apt install patchelf
```

#### Steps for overlay
1. Use script `setup.py` with option `--with-ros2`.

#### Steps for standalone
1. Use script `setup.py` with option `--with-ros2-standalone`
- You can specify run-time search path(s) of RGL dependencies by adding option `--lib-rpath <paths>`. This can be useful if you want RGL to search for ROS2 libraries in a different directory than RGL, e.g., if you work with a library providing its own ROS2 libraries such as [ROS2ForUnity](https://github.com/RobotecAI/ros2-for-unity).
2. Copy all ROS2 libraries from `<build-dir>/ros2_standalone/` to `libRobotecGPULidar.so` location (or location defined with `--lib-rpath`).

#### Tips for integrating RGL + ROS2 standalone with Unity and [ROS2ForUnity](https://github.com/RobotecAI/ros2-for-unity) plugin.
1. Build RGL with command:
```bash
./setup.py --with-ros2-standalone --lib-rpath \$ORIGIN/.plugin --make="-j"
```
*$ORIGIN represents the directory in which an object (library) originated. It is resolved at run-time.*
2. Copy library `<build-dir>/libRobotecGPULidar.so` to the appropriate directory in your RGLUnityPlugin.
3. Create a new directory named `.plugin` in location where `libRobotecGPULidar.so` is.
4. Copy all ROS2 libraries from `<build-dir>/ros2_standalone/` to the newly created `.plugin` directory.

Unity ignores dynamic libraries in directories named `.plugin`. This way, ROS2 standalone builds for `ROS2ForUnity` and `RGL` will be separated and not loaded twice by Unity.

### Windows

#### Prerequisites

- Requirements listed in the main [README](../README.md) (section `Building on Windows`).
- ROS2 installed on the system and sourced.
- Fixed ROS2 logging macros in rclcpp package to make it compile with C++20. More about this bug: [github PR](https://github.com/ros2/rclcpp/pull/2063).
- Use `fix_ros2_humble.py` script to apply those changes:
```bash
py ros2_standalone\fix_ros2_humble.py <your-path-to-ros2>
```
- For standalone build:
- If you have installed ROS2 from pre-built binaries, there is `rmw_cyclonedds_cpp` package missing. You need to build this one from the source:
1. Make sure you have installed [ROS2 prerequisites](https://docs.ros.org/en/humble/Installation/Alternatives/Windows-Development-Setup.html#installing-prerequisites).
2. Good practice is to disable Windows path limits, [see](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry).
3. Run `x64 Native Tools Command Prompt for VS 2019`, setup development folder (e.g., `C:\rosrmw`), clone ROS2 repos, and build:
```bash
md \rosrmw\src
cd \rosrmw
vcs import --input https://raw.githubusercontent.com/ros2/ros2/humble/ros2.repos src
colcon build --merge-install --packages-up-to rmw_cyclonedds_cpp
```
4. Before building RGL, source `rosrmw` workspace: `call C:\rosrmw\install\setup.bat`.


#### Steps for overlay
1. Run `x64 Native Tools Command Prompt for VS 2019` and navigate to RGL repository.
2. Run `python setup.py --with-ros2` command to build RGL with ROS2 extension.

#### Steps for standalone
1. Run `x64 Native Tools Command Prompt for VS 2019` and navigate to RGL repository.
2. Run `python setup.py --with-ros2-standalone` command to build RGL with ROS2 extension and install ROS2 libraries.
3. Copy all ROS2 libraries from `<build-dir>/ros2_standalone/` into `RobotecGPULidar.dll` location, or extend environment variable `Path` appropriately.

#### Tips for integrating RGL + ROS2 standalone with Unity and [ROS2ForUnity](https://github.com/RobotecAI/ros2-for-unity) plugin.
1. Build RGL with ROS2 standalone as described above.
2. Copy `<build-dir>/RobotecGPULidar.dll` and all depend libraries located in `<build-dir>` to appropriate directory in your RGLUnityPlugin.
3. Copy all ROS2 libraries from `<build-dir>/ros2_standalone/` to `Ros2ForUnity\Plugins\Windows\x86_64` directory. Skip for duplicates.

In this case, RGL's ROS2 standalone build is dependent on ROS2ForUnity's ROS2 standalone build. RobotecGPULidar library will find ROS2 because ROS2ForUnity sets environment variable `Path` for the ROS2 libraries.

## Usage

Each RGL node for ROS2 publishing must be connected to a format node that defines fields and their layout in the binary data. For example, to publish PointCloud2 message with fields XYZ and DISTANCE, the code should look as follow:
```c
...
rgl_node_t nodeFormat = nullptr, nodeRos2 = nullptr;
std::vector<rgl_field_t> fieldsToPublish = { RGL_FIELD_XYZ_F32, RGL_FIELD_DISTANCE_F32 };
rgl_node_points_format(&nodeFormat, fieldsToPublish.data(), fieldsToPublish.size())
rgl_node_points_ros2_publish(&nodeRos2, "example_topic", "example_frame");
rgl_graph_node_add_child(..., nodeFormat);
rgl_graph_node_add_child(nodeFormat, nodeRos2);
```
## API documentation
More details can be found [here](../include/rgl/api/extensions/ros2.h).
1 change: 1 addition & 0 deletions external/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ if (UNIX)
endif()

if (${RGL_BUILD_TESTS})
set(INSTALL_GTEST OFF CACHE INTERNAL "Disable installation of googletest")
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest
Expand Down
144 changes: 144 additions & 0 deletions ros2_standalone/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# This file is based on https://github.com/RobotecAI/ros2cs/blob/master/src/ros2cs/ros2cs_core/CMakeLists.txt

find_program(PATCHELF "patchelf")
if(UNIX)
if(NOT PATCHELF)
message(FATAL_ERROR "'patchelf' executable not found. Linux standalone version requires 'patchelf' for patching 'runpath' of certain libraries. You can install it via 'sudo apt install patchelf'")
else()
message("'patchelf' found in ${PATCHELF}")
endif()
endif()
set(REQ_STANDALONE_LIBS "")
set(REQ_STANDALONE_DLLS "")

# Get location for library target
macro(fetch_target_lib _target)
string(REGEX REPLACE "::" "_" _target_normalized ${_target})
set(_locations IMPORTED_LOCATION_NONE IMPORTED_LOCATION_NOCONFIG IMPORTED_LOCATION_RELEASE IMPORTED_LOCATION_RELWITHDEBINFO IMPORTED_LOCATION_DEBUG)
foreach(_location ${_locations})
get_target_property(${_target_normalized}_LIB_PATH ${_target} ${_location})
if(NOT "${${_target_normalized}_LIB_PATH}" STREQUAL "${_target_normalized}_LIB_PATH-NOTFOUND")
break()
endif()
endforeach()
endmacro()

# Extend REQ_STANDALONE_LIBS with _library_name dependencies
macro(get_standalone_dependencies _library_name)
find_package(${_library_name} REQUIRED)

# Get cyclonedds DDSC
if("${_library_name}" STREQUAL "CycloneDDS")
if(WIN32)
fetch_target_lib(CycloneDDS::ddsc)
fetch_target_lib(CycloneDDS::idl)
list(APPEND REQ_STANDALONE_LIBS
${CycloneDDS_ddsc_LIB_PATH}
${CycloneDDS_idl_LIB_PATH}
${CycloneDDS_dds_security_ac_LIB_PATH}
${CycloneDDS_dds_security_auth_LIB_PATH}
${CycloneDDS_dds_security_crypto_LIB_PATH})
elseif(UNIX)
fetch_target_lib(CycloneDDS::ddsc)
list(APPEND REQ_STANDALONE_LIBS ${CycloneDDS_ddsc_LIB_PATH})
endif()
endif()

# Get rmw_cyclonedds_cpp
if("${_library_name}" STREQUAL "rmw_cyclonedds_cpp")
fetch_target_lib(rmw_cyclonedds_cpp::rmw_cyclonedds_cpp)
list(APPEND REQ_STANDALONE_LIBS ${rmw_cyclonedds_cpp_rmw_cyclonedds_cpp_LIB_PATH})
endif()

# We skip python libs
set(PYTHON_LIB_REGEX ".*python[0-9]*\.[0-9]*\.so")
if(WIN32)
set(PYTHON_LIB_REGEX ".*(l|L)ib(s|)\/python[0-9]*\.lib" )
endif()
foreach(entry ${${_library_name}_LIBRARIES})
string(REGEX MATCH ${PYTHON_LIB_REGEX} _found ${entry})
if(_found STREQUAL "")
list(APPEND REQ_STANDALONE_LIBS ${entry})
endif()
endforeach()
endmacro()

# Install all libraries listed in REQ_STANDALONE_LIBS
macro(install_standalone_dependencies)
# Filter valid libraries
list(FILTER REQ_STANDALONE_LIBS INCLUDE REGEX ".*(lib|dll|so)(\.[0-9])*$")
list(REMOVE_DUPLICATES REQ_STANDALONE_LIBS)

if(WIN32)
foreach(lib_path ${REQ_STANDALONE_LIBS})
string(REGEX REPLACE "\/(l|L)ib\/" "/bin/" bin_path ${lib_path})
string(REGEX REPLACE "\.lib$" ".dll" dll_path ${bin_path})
list(APPEND REQ_STANDALONE_DLLS ${dll_path})
endforeach()
install(FILES ${REQ_STANDALONE_DLLS}
DESTINATION ros2_standalone
)
elseif(UNIX)
set(_resolvedFiles "")
foreach(lib_path ${REQ_STANDALONE_LIBS})
# Generate soversion files (.so.<major>). Some libs links to soversion symlinks which are not returned by find_package by default.
get_filename_component(_resolvedFile "${lib_path}" REALPATH)
list(APPEND _resolvedFiles "${_resolvedFile}")
endforeach()
install(FILES ${_resolvedFiles}
DESTINATION ros2_standalone
)

# Fix soversion files
foreach(_resolvedFile ${_resolvedFiles})
if("${_resolvedFile}" MATCHES "so(\.[0-9]+)+$")
# Get file path without so
string(FIND "${_resolvedFile}" ".so." _findPos)
string(SUBSTRING "${_resolvedFile}" 0 ${_findPos} _cutted)

set(_remainingPath "${_resolvedFile}")
while("${_remainingPath}" MATCHES "so(\.[0-9]*)+$")
string(FIND "${_remainingPath}" "." _lastDotPos REVERSE)
string(SUBSTRING "${_remainingPath}" 0 ${_lastDotPos} _tempPos)
get_filename_component(_libPathFilename "${_tempPos}" NAME)
get_filename_component(_resolvedFilename "${_resolvedFile}" NAME)
install(CODE "execute_process(COMMAND ln -s ${_resolvedFilename} ${_libPathFilename} WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/ros2_standalone ERROR_QUIET)")
set(_remainingPath "${_tempPos}")
endwhile()
endif()
endforeach()

# rpath for each standalone lib must be updated so all the libs will see each other
install(CODE
"execute_process(COMMAND find ${CMAKE_INSTALL_PREFIX}/ros2_standalone -name *.so -exec patchelf --set-rpath $ORIGIN:. {} \;)"
)
# soversion files as well
install(CODE
"execute_process(COMMAND find ${CMAKE_INSTALL_PREFIX}/ros2_standalone -name *.so.* -exec patchelf --set-rpath $ORIGIN:. {} \;)"
)
endif()
endmacro()

# Libraries required by RGL
set(ros2_standalone_libs
rcl
rclcpp
sensor_msgs
visualization_msgs
rmw_implementation
FastRTPS
rmw_fastrtps_cpp
CycloneDDS
rmw_cyclonedds_cpp
rmw_dds_common
rosidl_runtime_c
rosidl_typesupport_c
rosidl_typesupport_cpp
rosidl_typesupport_introspection_c
rosidl_typesupport_introspection_cpp
)

foreach(ros2_standalone_lib ${ros2_standalone_libs})
get_standalone_dependencies(${ros2_standalone_lib})
endforeach()
install_standalone_dependencies()
Loading

0 comments on commit 6fa2364

Please sign in to comment.