ros2-dotnet / ros2_dotnet

.NET bindings for ROS2
Apache License 2.0
136 stars 54 forks source link

How to add reference to external dlls in cmakelist #95

Open michaelchi08 opened 1 year ago

michaelchi08 commented 1 year ago

Hi,

Thanks for providing this project for developers who want to use C# to build ros2 apps. I'm new to cmake and i wanted to add my own library dll to the publisher and listener node. What do i need to add to cmakelists so it finds the dll and its location?

where are these variables set rcldotnet_common_ASSEMBLIES_DLL, i'm assuming these are the locations for cmake to go and find the dlls !?

set(_assemblies_dep_dlls ${rcldotnet_common_ASSEMBLIES_DLL} ${rcldotnet_ASSEMBLIES_DLL} ${std_msgs_ASSEMBLIES_DLL} ${rcldotnet_ASSEMBLIES_DLL} )

Thanks for your help, Michael

michaelchi08 commented 1 year ago

i'm using foxy

hoffmann-stefan commented 1 year ago

Hi @michaelchi08

Im not that fluent with cmake either, but here are some pointers based on my understanding.

Building and referencing libraries in ROS workspaces with CMAKE

where are these variables set rcldotnet_common_ASSEMBLIES_DLL, i'm assuming these are the locations for cmake to go and find the dlls !?

The variables are set in the correspoinding find_package() function call. When sourcing your workspace that was built with colcon/ament the CMAKE_PREFIX_PATH gets populated to include the right directories for find_package() to find an ament_cmake_export_assemblies-extras.cmake for that package that defines those variables

find_package(rcldotnet_common REQUIRED)

find_package(std_msgs REQUIRED)
find_package(std_srvs REQUIRED)
find_package(rcldotnet REQUIRED)
$ echo $CMAKE_PREFIX_PATH
[...]:$YOUR_ROS2_DOTNET_WS/install/rcldotnet_common:[...]

$ cat $YOUR_ROS2_DOTNET_WS/install/rcldotnet_common/share/rcldotnet_common/cmake/ament_cmake_export_assemblies-extras.cmake
# generated from ament_cmake_export_assemblies/cmake/template/ament_cmake_export_assemblies.cmake.in

set(_exported_assemblies_dll "${rcldotnet_common_DIR}/../../../lib/rcldotnet_common/dotnet/rcldotnet_common.dll")

# append absolute assemblies to rcldotnet_common_ASSEMBLIES_DLL
# warn about not existing paths
if(NOT "${_exported_assemblies_dll} " STREQUAL " ")
  find_package(ament_cmake_core QUIET REQUIRED)
  foreach(_exported_assembly_dll ${_exported_assemblies_dll})
    if(NOT EXISTS "${_exported_assembly_dll}")
      message(WARNING "Package 'rcldotnet_common' exports the DLL assembly '${_exported_assembly_dll}' which doesn't exist")
    endif()
    normalize_path(_exported_assembly_dll "${_exported_assembly_dll}")
    list(APPEND rcldotnet_common_ASSEMBLIES_DLL "${_exported_assembly_dll}")
  endforeach()
endif()

set(_exported_assemblies_nuget "")

# append absolute assemblies to rcldotnet_common_ASSEMBLIES_NUGET
# warn about not existing paths
if(NOT "${_exported_assemblies_nuget} " STREQUAL " ")
  find_package(ament_cmake_core QUIET REQUIRED)
  foreach(_exported_assembly_nuget ${_exported_assemblies_nuget})
    if(NOT EXISTS "${_exported_assembly_nuget}")
      message(WARNING "Package 'rcldotnet_common' exports the NuGet assembly '${_exported_assembly_nuget}' which doesn't exist")
    endif()
    normalize_path(_exported_assembly_nuget "${_exported_assembly_nuget}")
    list(APPEND rcldotnet_common_ASSEMBLIES_NUGET "${_exported_assembly_nuget}")
  endforeach()
endif()

This is for consuming dotnet libraries built using colcon/ament as part of your ROS2 workspace. If you want to build dotnet libraries as part of your workspace see how the rcldotnet_common ROS package is set up as an example: https://github.com/ros2-dotnet/ros2_dotnet/tree/master/rcldotnet_common

Consume dotnet libraries that are built outside of ROS workspaces

You most likely want to include some library of yours that was built outside of a ROS2 workspace. For this you can either include the absolute path in the _assemblies_dep_dlls using either hardcoded paths in your project or maybe using an environment variable and string concat functions in cmake.

The better variant would be to include your external dotnet libraries as NuGet packages in the CMakeLists.txt:

The test for rcldotnet do it like this: https://github.com/ros2-dotnet/ros2_dotnet/blob/master/rcldotnet/CMakeLists.txt#L100-L109

Here would be the cange for the talker example:

add_dotnet_executable(rcldotnet_talker
  RCLDotnetTalker.cs
  INCLUDE_DLLS
  ${_assemblies_dep_dlls}
  INCLUDE_REFERENCES
  "Newtonsoft.Json=13.0.3-beta1"
)

The NuGet packages don't need to be public, you can use our own private package feed or configure a folder where dotnet restore searches for them.

Build inside ROS workspace but using a *.csproj

We use another mechanism in our internal code to build executables and libraries inside ROS workspaces, but using *.csproj files to specify NuGet packages and other dotnet project settings. This is the branch we are using internally (not yet upstramed): https://github.com/schiller-de/dotnet_cmake_module/commits/feature-include-csproj. See this PR for our fork for more description on how this works: https://github.com/schiller-de/dotnet_cmake_module/pull/1.

michaelchi08 commented 1 year ago

Thanks for the response and explanation @hoffmann-stefan . I tried the following(absolute reference for a quick try): add_dotnet_executable(rcldotnet_talker RCLDotnetTalker.cs INCLUDE_DLLS ${_assemblies_dep_dlls} INCLUDE_REFERENCES "/home/ros/Projects/hello/Motion.dll" ) but got this build error..

'starting >>> sensor_msgs_py --- stderr: rcldotnet_examples
Included assemblies: /home/ros/ros2_dotnet_ws/install/rcldotnet_common/lib/rcldotnet_common/dotnet/rcldotnet_common.dll;/home/ros/ros2_dotnet_ws/install/rcldotnet/lib/rcldotnet/dotnet/rcldotnet_assemblies.dll;/home/ros/ros2_dotnet_ws/install/std_msgs/lib/std_msgs/dotnet/std_msgs_assemblies.dll;/home/ros/ros2_dotnet_ws/install/rcldotnet/lib/rcldotnet/dotnet/rcldotnet_assemblies.dll CMake Error at /home/ros/ros2_dotnet_ws/install/dotnet_cmake_module/share/dotnet_cmake_module/cmake/Modules/dotnet/UseCSharpProjectBuilder.cmake:41 (list): list index: 1 out of range (-1, 0) Call Stack (most recent call first): /home/ros/ros2_dotnet_ws/install/dotnet_cmake_module/share/dotnet_cmake_module/cmake/Modules/FindDotNETExtra.cmake:47 (csharp_add_project) CMakeLists.txt:27 (add_dotnet_executable)

CMake Error at /home/ros/ros2_dotnet_ws/install/dotnet_cmake_module/share/dotnet_cmake_module/cmake/Modules/dotnet/UseCSharpProjectBuilder.cmake:41 (list): list index: 1 out of range (-1, 0) Call Stack (most recent call first): /home/ros/ros2_dotnet_ws/install/dotnet_cmake_module/share/dotnet_cmake_module/cmake/Modules/FindDotNETExtra.cmake:47 (csharp_add_project) CMakeLists.txt:34 (add_dotnet_executable)

make: *** [Makefile:250: cmake_check_build_system] Error 1

Failed <<< rcldotnet_examples [0.65s, exited with code 2]'

hoffmann-stefan commented 1 year ago

@michaelchi08

The dll's need to go into the INCLUDE_DLLS parameter not into INCLUDE_REFERENCES. INCLUDE_REFERENCES are only for NuGet packages.

Something like that:

set(_assemblies_dep_dlls
  ${rcldotnet_common_ASSEMBLIES_DLL}
  ${rcldotnet_ASSEMBLIES_DLL}
  ${std_msgs_ASSEMBLIES_DLL}
  ${rcldotnet_ASSEMBLIES_DLL}
  "/home/ros/Projects/hello/Motion.dll"
)

add_dotnet_executable(rcldotnet_talker RCLDotnetTalker.cs INCLUDE_DLLS ${_assemblies_dep_dlls})

Some other tip: Use colcon build --event-handlers console_direct+ to print out all errors directly. If it get's to build the C# code using dotnet build the output won't be visible by default. colcon only prints stderr, but the dotnet tool only outputs to stdout.