From 6fa2364063dc249ccd74fccf001dd7145e731810 Mon Sep 17 00:00:00 2001 From: Mateusz Szczygielski <112629916+msz-rai@users.noreply.github.com> Date: Wed, 25 Jan 2023 15:58:23 +0100 Subject: [PATCH] Support for ROS2 standalone (#79) * 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 * 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 <112629916+msz-rai@users.noreply.github.com> * Inform about default --make and --ninja args Co-authored-by: Mateusz Szczygielski <112629916+msz-rai@users.noreply.github.com> * 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 --- CMakeLists.txt | 14 ++- DockerfileLatest | 6 +- DockerfileMinimal | 6 +- README.md | 44 +++---- docs/Ros2Extension.md | 112 ++++++++++++++++++ external/CMakeLists.txt | 1 + ros2_standalone/CMakeLists.txt | 144 +++++++++++++++++++++++ ros2_standalone/fix_ros2_humble.py | 32 ++++++ setup.bash | 86 -------------- setup.py | 177 +++++++++++++++++++++++++++++ test/CMakeLists.txt | 8 +- 11 files changed, 514 insertions(+), 116 deletions(-) create mode 100644 docs/Ros2Extension.md create mode 100644 ros2_standalone/CMakeLists.txt create mode 100644 ros2_standalone/fix_ros2_humble.py delete mode 100755 setup.bash create mode 100755 setup.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 6def56e0..cd964135 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -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) @@ -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() diff --git a/DockerfileLatest b/DockerfileLatest index 1e713ae6..e8f31968 100644 --- a/DockerfileLatest +++ b/DockerfileLatest @@ -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 diff --git a/DockerfileMinimal b/DockerfileMinimal index 1b4e5d13..6f265b36 100644 --- a/DockerfileMinimal +++ b/DockerfileMinimal @@ -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 diff --git a/README.md b/README.md index 6117a193..62251f19 100644 --- a/README.md +++ b/README.md @@ -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: @@ -61,7 +66,7 @@ Build instructions: 3. `export OptiX_INSTALL_DIR=` 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 @@ -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=`. -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 diff --git a/docs/Ros2Extension.md b/docs/Ros2Extension.md new file mode 100644 index 00000000..8a73db3d --- /dev/null +++ b/docs/Ros2Extension.md @@ -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 `. 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 `/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 `/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 `/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 + ``` +- 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 `/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 `/RobotecGPULidar.dll` and all depend libraries located in `` to appropriate directory in your RGLUnityPlugin. +3. Copy all ROS2 libraries from `/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 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). diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index c9b0ad52..0ccab08b 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -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 diff --git a/ros2_standalone/CMakeLists.txt b/ros2_standalone/CMakeLists.txt new file mode 100644 index 00000000..92b9a414 --- /dev/null +++ b/ros2_standalone/CMakeLists.txt @@ -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.). 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() diff --git a/ros2_standalone/fix_ros2_humble.py b/ros2_standalone/fix_ros2_humble.py new file mode 100644 index 00000000..16d6e015 --- /dev/null +++ b/ros2_standalone/fix_ros2_humble.py @@ -0,0 +1,32 @@ +import sys +import os.path as path +import platform + +if len(sys.argv) != 2 or platform.system() != "Windows": + print("""\ +This script will apply changes to rclcpp package from https://github.com/ros2/rclcpp/pull/2063 +to make it compile with C++20 on Windows. + +Usage: fix_ros2_humble path-to-ros2 +""") + sys.exit(0) + +fileToModify = path.join(sys.argv[1], "include\\rclcpp\\rclcpp\\logging.hpp") +print(f"Looking for file: {fileToModify}") + +if not path.isfile(fileToModify): + print("Unable to find file to apply changes. Make sure your ROS2 path is correct.") + sys.exit(1) + +toFind = "::std::is_same::type>::type, \\" +toReplace = "::std::is_same>, \\" + +with open(fileToModify, "r") as file: + filedata = file.read() + +filedata = filedata.replace(toFind, toReplace) + +with open(fileToModify, "w") as file: + file.write(filedata) + +print("Changes has been applied.") diff --git a/setup.bash b/setup.bash deleted file mode 100755 index eebd5f23..00000000 --- a/setup.bash +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - -CUDA_MIN_VER_MAJOR="11" -CUDA_MIN_VER_MINOR="2" -VCPKG_INSTALL_DIR="external/vcpkg" -VCPKG_TAG="2022.08.15" -INSIDE_DOCKER=false - -if [ -f /.dockerenv ]; then - VCPKG_INSTALL_DIR="/rgldep/vcpkg" - INSIDE_DOCKER=true - echo "Setup inside docker" -fi - -CMAKE_ARGS=() -MAKE_ARGS=() -COLLECT_CMAKE_ARGS=false -COLLECT_MAKE_ARGS=false -DO_MAKE=false -DO_CMAKE=false -BUILD_DIR=build - -for arg in "$@" -do - if [ "$arg" = "--cmake" ]; then - COLLECT_CMAKE_ARGS=true && COLLECT_MAKE_ARGS=false && DO_CMAKE=true && continue; - fi - if [ "$arg" = "--make" ]; then - COLLECT_MAKE_ARGS=true && COLLECT_CMAKE_ARGS=false && DO_MAKE=true && continue; - fi - if [ "$COLLECT_CMAKE_ARGS" = true ]; then CMAKE_ARGS+=("$arg") && continue; fi - if [ "$COLLECT_MAKE_ARGS" = true ]; then MAKE_ARGS+=("$arg") && continue; fi - if [[ ("${COLLECT_CMAKE_ARGS}" != true) && ("${COLLECT_MAKE_ARGS}" != true) ]]; then - BUILD_DIR="${arg}" && continue; - fi -done - -cd "$(dirname "$0")" - -# Check CUDA -CUDA_VERSION=$(nvcc --version 2>/dev/null | grep -E -o "V[0-9]+.[0-9]+.[0-9]+" | cut -c2-) -if [ -z "$CUDA_VERSION" ]; then echo "CUDA not found!" && exit 1; fi -CUDA_MAJOR=$(echo "$CUDA_VERSION" | cut -d. -f1) -CUDA_MINOR=$(echo "$CUDA_VERSION" | cut -d. -f2) -if [ "$CUDA_MAJOR" -lt $CUDA_MIN_VER_MAJOR ] || - { [ "$CUDA_MAJOR" -eq $CUDA_MIN_VER_MAJOR ] && [ "$CUDA_MINOR" -lt $CUDA_MIN_VER_MINOR ]; }; then - echo "CUDA missing or CUDA version not supported! Get CUDA $CUDA_MIN_VER_MAJOR.$CUDA_MIN_VER_MINOR+"; - exit 1; -fi - -# Check OptiX_INSTALL_DIR if building RGL -if [ -z "$OptiX_INSTALL_DIR" ] && { [ "$DO_CMAKE" = true ] || [ "$DO_MAKE" = true ]; }; then - echo "OptiX not found! Make sure you have exported environment variable OptiX_INSTALL_DIR"; - exit 1; -fi - -# Install PCL using vcpkg -if [ ! -d "$VCPKG_INSTALL_DIR" ]; then - if [ "$INSIDE_DOCKER" = false ]; then # Inside docker already provided - echo "Installing dependencies for vcpkg..." - sudo apt update - sudo apt install git curl zip unzip tar freeglut3-dev libglew-dev libglfw3-dev - fi - git clone -b $VCPKG_TAG --single-branch --depth 1 https://github.com/microsoft/vcpkg $VCPKG_INSTALL_DIR -fi -if [ ! -f $VCPKG_INSTALL_DIR"/vcpkg" ]; then $VCPKG_INSTALL_DIR"/bootstrap-vcpkg.sh"; fi - -$VCPKG_INSTALL_DIR"/vcpkg" "install" "--clean-after-build" "pcl[core,visualization]" - -# Build -if [ ! -d "${BUILD_DIR}" ]; then mkdir "${BUILD_DIR}"; fi -cd "${BUILD_DIR}" -if [ "$DO_CMAKE" = true ]; then - cmake .. -DCMAKE_TOOLCHAIN_FILE=$VCPKG_INSTALL_DIR"/scripts/buildsystems/vcpkg.cmake" "${CMAKE_ARGS[@]}"; -else - echo "--- IMPORTANT --- -Remember to pass vcpkg's CMAKE_TOOLCHAIN_FILE to cmake. See: -cmake .. -DCMAKE_TOOLCHAIN_FILE=$VCPKG_INSTALL_DIR/scripts/buildsystems/vcpkg.cmake -" -fi -if [ "$DO_MAKE" = true ]; then - make "${MAKE_ARGS[@]}"; -fi diff --git a/setup.py b/setup.py new file mode 100755 index 00000000..b9acf90f --- /dev/null +++ b/setup.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +import os +import platform +import sys +import re +import subprocess +import shutil +import argparse + + +class Config: + # Default values for Linux + CUDA_MIN_VER_MAJOR = 11 + CUDA_MIN_VER_MINOR = 2 + CUDA_MIN_VER_PATCH = 0 + CMAKE_GENERATOR = "'Unix Makefiles'" + VCPKG_INSTALL_DIR = os.path.join("external", "vcpkg") + VCPKG_TAG = "2022.08.15" + VCPKG_EXEC = "vcpkg" + VCPKG_BOOTSTRAP = "bootstrap-vcpkg.sh" + VCPKG_PLATFORM_SPEC = "" + + def __init__(self): + # Platform-dependent configuration + if inside_docker(): + self.VCPKG_INSTALL_DIR = os.path.join("/rgldep", "vcpkg") + + if on_windows(): + self.CUDA_MIN_VER_MINOR = 4 + self.CUDA_MIN_VER_PATCH = 152 # patch for CUDA 11.4 Update 4 + self.CMAKE_GENERATOR = "Ninja" + self.VCPKG_EXEC = "vcpkg.exe" + self.VCPKG_BOOTSTRAP = "bootstrap-vcpkg.bat" + self.VCPKG_PLATFORM_SPEC = ":x64-windows" + + +def main(): + cfg = Config() + # Parse arguments + parser = argparse.ArgumentParser(description="Helper script to build RGL.") + parser.add_argument("--build-dir", type=str, nargs=1, default="build", + help="Path to build directory. Default: 'build'") + parser.add_argument("--install-deps", action='store_true', + help="Install RGL dependencies and exit") + parser.add_argument("--clean-build", action='store_true', + help="Remove build directory before cmake") + parser.add_argument("--with-ros2", action='store_true', + help="Build RGL with ROS2 extension") + parser.add_argument("--with-ros2-standalone", action='store_true', + help="Build RGL with ROS2 extension in standalone mode") + parser.add_argument("--cmake", type=str, default="", + help="Pass arguments to cmake. Usage: --cmake=\"args...\"") + if on_linux(): + parser.add_argument("--make", type=str, default=f"-j{os.cpu_count()}", dest="build_args", + help="Pass arguments to make. Usage: --make=\"args...\". Defaults to \"-j \"") + parser.add_argument("--lib-rpath", type=str, nargs='*', + help="Add run-time search path(s) for RGL library") + if on_windows(): + parser.add_argument("--ninja", type=str, default=f"-j{os.cpu_count()}", dest="build_args", + help="Pass arguments to ninja. Usage: --ninja=\"args...\". Defaults to \"-j \"") + args = parser.parse_args() + + # Install RGL dependencies + if args.install_deps: + # Clone vcpkg + if not os.path.isdir(cfg.VCPKG_INSTALL_DIR): + if on_linux() and not inside_docker(): # Inside docker already installed + print("Installing dependencies for vcpkg...") + run_system_command("sudo apt update") + run_system_command("sudo apt install git curl zip unzip tar freeglut3-dev libglew-dev libglfw3-dev") + run_subprocess_command(f"git clone -b {cfg.VCPKG_TAG} --single-branch --depth 1 https://github.com/microsoft/vcpkg {cfg.VCPKG_INSTALL_DIR}") + # Bootstrap vcpkg + if not os.path.isfile(os.path.join(cfg.VCPKG_INSTALL_DIR, cfg.VCPKG_EXEC)): + run_subprocess_command(f"{os.path.join(cfg.VCPKG_INSTALL_DIR, cfg.VCPKG_BOOTSTRAP)}") + # Install dependencies via vcpkg + run_subprocess_command(f"{os.path.join(cfg.VCPKG_INSTALL_DIR, cfg.VCPKG_EXEC)} install --clean-after-build pcl[core,visualization]{cfg.VCPKG_PLATFORM_SPEC}") + return 0 + + # Check CUDA + def is_cuda_version_ok(): + nvcc_process = subprocess.run("nvcc --version", shell=True, stdout=subprocess.PIPE) + nvcc_ver_match = re.search("V[0-9]+.[0-9]+.[0-9]+", nvcc_process.stdout.decode("utf-8")) + if not nvcc_ver_match: + raise RuntimeError("CUDA not found") + major = int(nvcc_ver_match[0].split(".")[0][1:]) # [1:] to remove char 'v' + minor = int(nvcc_ver_match[0].split(".")[1]) + patch = int(nvcc_ver_match[0].split(".")[2]) + print(f"Found CUDA {major}.{minor}.{patch}") + for (actual, expected) in [(major, cfg.CUDA_MIN_VER_MAJOR), (minor, cfg.CUDA_MIN_VER_MINOR), (patch, cfg.CUDA_MIN_VER_PATCH)]: + if actual > expected: + return True + if actual < expected: + return False + return True + + if not is_cuda_version_ok(): + raise RuntimeError(f"CUDA version not supported! Get CUDA {cfg.CUDA_MIN_VER_MAJOR}.{cfg.CUDA_MIN_VER_MINOR}.{cfg.CUDA_MIN_VER_PATCH}+") + + # Check OptiX_INSTALL_DIR + if os.environ["OptiX_INSTALL_DIR"] == "": + raise RuntimeError("OptiX not found! Make sure you have exported environment variable OptiX_INSTALL_DIR") + + # Go to script directory + os.chdir(sys.path[0]) + + # Prepare build directory + if args.clean_build and os.path.isdir(args.build_dir): + shutil.rmtree(args.build_dir, ignore_errors=True) + if not os.path.isdir(args.build_dir): + os.makedirs(args.build_dir) + + # Extend Path with libRobotecGPULidar location to link tests properly during the build on Windows + if on_windows(): + os.environ["Path"] = os.environ["Path"] + ";" + os.path.join(os.getcwd(), args.build_dir) + + # Build + cmake_args = [ + f"-DCMAKE_TOOLCHAIN_FILE={os.path.join(cfg.VCPKG_INSTALL_DIR, 'scripts', 'buildsystems', 'vcpkg.cmake')}", + f"-DRGL_BUILD_ROS2_EXTENSION={'ON' if args.with_ros2 else 'OFF'}", + f"-DRGL_BUILD_ROS2_EXTENSION_STANDALONE={'ON' if args.with_ros2_standalone else 'OFF'}" + ] + + if on_linux(): + # Set rpaths + if args.lib_rpath is not None: + linker_rpath_flags = [] + for rpath in args.lib_rpath: + rpath = rpath.replace("$ORIGIN", "\\$ORIGIN") # cmake should not treat this as variable + linker_rpath_flags.append(f"-Wl,-rpath={rpath}") + cmake_args.append(f"-DCMAKE_SHARED_LINKER_FLAGS=\"{' '.join(linker_rpath_flags)}\"") + + if on_windows(): + cmake_args.append("-DRGL_BUILD_TOOLS=OFF") # Tools are not available on Windows + + # Append user args, possibly overwriting + cmake_args.append(args.cmake) + + cmake_args = " ".join(cmake_args) + run_subprocess_command(f"cmake -B {args.build_dir} -G {cfg.CMAKE_GENERATOR} --install-prefix {os.path.join(os.getcwd(), args.build_dir)} {cmake_args}") + run_subprocess_command(f"cmake --build {args.build_dir} -- {args.build_args}") + + if args.with_ros2_standalone: + run_subprocess_command(f"cmake --install {args.build_dir}") + + +def on_linux(): + return platform.system() == "Linux" + + +def on_windows(): + return platform.system() == "Windows" + + +def inside_docker(): + path = "/proc/self/cgroup" + return ( + os.path.exists("/.dockerenv") or + os.path.isfile(path) and any("docker" in line for line in open(path)) + ) + + +def run_subprocess_command(command: str, shell=True, stderr=sys.stderr, stdout=sys.stdout): + print(f"Executing command: '{command}'") + process = subprocess.Popen(command, shell=shell, stderr=stderr, stdout=stdout) + process.wait() + if process.returncode != 0: + raise RuntimeError(f"Failed to execute command: '{command}'") + + +def run_system_command(command: str): + print(f"Executing command: '{command}'") + if os.system(command) != 0: + raise RuntimeError(f"Failed to execute command: '{command}'") + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 132eec43..d38a33f1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.16) set(RGL_TEST_FILES src/graphTest.cpp src/apiReadmeExample.cpp - src/VArrayTest.cpp # src/apiSurfaceTests.cpp # src/features/range.cpp # src/features/gaussianNoise.cpp @@ -13,6 +12,13 @@ set(RGL_TEST_FILES src/testMat3x4f.cpp ) +# Only Linux +if ((NOT WIN32)) + list(APPEND RGL_TEST_FILES + src/VArrayTest.cpp + ) +endif() + # On Windows, tape is not available since it uses Linux sys-calls (mmap) # AutoTape interferes with tape tests (e.g. double rgl_tape_record_begin()) which is non-trivial to fix. if ((NOT WIN32) AND (NOT RGL_AUTO_TAPE_PATH))