diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0d42d1b..47aeb24 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,6 +14,7 @@ jobs: - humble - iron - jazzy + - kilted include: - ros_distribution: foxy docker_image: ros:foxy-ros-base @@ -23,6 +24,8 @@ jobs: docker_image: ros:iron-ros-base - ros_distribution: jazzy docker_image: ros:jazzy-ros-base + - ros_distribution: kilted + docker_image: ros:kilted-ros-base container: image: ${{ matrix.docker_image }} steps: @@ -68,7 +71,7 @@ jobs: RMW_IMPLEMENTATION: rmw_fastrtps_cpp ROS_LOCALHOST_ONLY: 0 run: | - . /opt/ros/${{ matrix.ros_distribution }}/setup.sh + . /opt/ros/${{ matrix.ros_distribution }}/setup.sh && \ dotnet test -c Release -p:BuildInParallel=false --no-build --verbosity normal --blame-crash-dump-type mini --blame-hang-dump-type none --blame-hang-timeout 1m - name: Test (cyclonedds) @@ -77,7 +80,7 @@ jobs: RMW_IMPLEMENTATION: rmw_cyclonedds_cpp ROS_LOCALHOST_ONLY: 0 run: | - . /opt/ros/${{ matrix.ros_distribution }}/setup.sh + . /opt/ros/${{ matrix.ros_distribution }}/setup.sh && \ dotnet test -c Release -p:BuildInParallel=false --no-build --verbosity normal --blame-crash-dump-type mini --blame-hang-dump-type none --blame-hang-timeout 1m windows: @@ -90,7 +93,11 @@ jobs: - humble - iron - jazzy + - kilted include: + - ros_distribution: kilted + ros_archive: https://github.com/ros2/ros2/releases/download/release-kilted-20250728/ros2-kilted-20250728-windows-release-amd64.zip + is_legacy_distro: false - ros_distribution: jazzy ros_archive: https://github.com/ros2/ros2/releases/download/release-jazzy-20250820/ros2-jazzy-20250820-windows-release-amd64.zip is_legacy_distro: false @@ -107,7 +114,20 @@ jobs: - uses: actions/checkout@v3 with: submodules: true - + + - name: Restore cached ROS installation + id: ros-cache + uses: actions/cache@v3 + with: + path: c:/dev/${{ matrix.ros_distribution }}/ + key: ${{ matrix.ros_archive }} + + - name: Download and install ROS + if: ${{ steps.ros-cache.outputs.cache-hit != 'true' }} + run: | + Invoke-WebRequest -Uri ${{ matrix.ros_archive }} -OutFile ros2-release.zip + Expand-Archive -Path ros2-release.zip c:/dev/${{ matrix.ros_distribution }} + - name: Install dependencies (Legacy) if: ${{ matrix.is_legacy_distro }} run: | @@ -116,27 +136,28 @@ jobs: choco install --limit-output --no-progress -y -s . tinyxml2 choco install --limit-output --no-progress -y openssl --version 1.1.1.2100 - - name: Install dependencies + - name: Install Pixi if: ${{ !matrix.is_legacy_distro }} run: | Set-ExecutionPolicy ByPass irm -useb https://pixi.sh/install.ps1 | iex - irm https://raw.githubusercontent.com/ros2/ros2/refs/heads/jazzy/pixi.toml -OutFile pixi.toml - $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") - pixi install - - name: Restore cached ROS installation - id: ros-cache + - name: Restore cached Pixi dependencies + if: ${{ !matrix.is_legacy_distro }} + id: pixi-cache uses: actions/cache@v3 with: - path: c:/dev/${{ matrix.ros_distribution }}/ - key: ${{ matrix.ros_archive }} + path: c:/dev/pixi/ + key: pixi-${{ matrix.ros_distribution }} - - name: Download and install ROS - if: ${{ steps.ros-cache.outputs.cache-hit != 'true' }} + - name: Install dependencies + if: ${{ !matrix.is_legacy_distro }} run: | - Invoke-WebRequest -Uri ${{ matrix.ros_archive }} -OutFile ros2-release.zip - Expand-Archive -Path ros2-release.zip c:/dev/${{ matrix.ros_distribution }} + mkdir c:/dev/pixi/ + cd c:/dev/pixi/ + irm https://raw.githubusercontent.com/ros2/ros2/refs/heads/${{ matrix.ros_distribution }}/pixi.toml -OutFile pixi.toml + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") + pixi install - name: Setup .NET 8.0 uses: actions/setup-dotnet@v3 @@ -167,8 +188,8 @@ jobs: RMW_IMPLEMENTATION: rmw_fastrtps_cpp ROS_LOCALHOST_ONLY: 0 run: | - refreshenv - call c:/dev/${{ matrix.ros_distribution }}/ros2-windows/setup.bat + refreshenv && ^ + call c:/dev/${{ matrix.ros_distribution }}/ros2-windows/setup.bat && ^ dotnet test -c Release -p:BuildInParallel=false --no-build --verbosity normal --blame-crash-dump-type mini --blame-hang-dump-type none --blame-hang-timeout 1m - name: Test (cyclonedds) (Legacy) @@ -179,8 +200,8 @@ jobs: RMW_IMPLEMENTATION: rmw_cyclonedds_cpp ROS_LOCALHOST_ONLY: 0 run: | - refreshenv - call c:/dev/${{ matrix.ros_distribution }}/ros2-windows/setup.bat + refreshenv && ^ + call c:/dev/${{ matrix.ros_distribution }}/ros2-windows/setup.bat && ^ dotnet test -c Release -p:BuildInParallel=false --no-build --verbosity normal --blame-crash-dump-type mini --blame-hang-dump-type none --blame-hang-timeout 1m - name: Test (fastrtps) @@ -191,10 +212,9 @@ jobs: RMW_IMPLEMENTATION: rmw_fastrtps_cpp ROS_LOCALHOST_ONLY: 0 run: | - refreshenv - pixi shell - call c:/dev/${{ matrix.ros_distribution }}/ros2-windows/setup.bat - dotnet test -c Release -p:BuildInParallel=false --no-build --verbosity normal --blame-crash-dump-type mini --blame-hang-dump-type none --blame-hang-timeout 1m + refreshenv && ^ + pixi run --manifest-path "c:/dev/pixi/" ^ + cmd /c "call c:/dev/${{ matrix.ros_distribution }}/ros2-windows/setup.bat && dotnet test -c Release -p:BuildInParallel=false --no-build --verbosity normal --blame-crash-dump-type mini --blame-hang-dump-type none --blame-hang-timeout 1m" - name: Test (cyclonedds) if: ${{ !matrix.is_legacy_distro }} @@ -204,10 +224,9 @@ jobs: RMW_IMPLEMENTATION: rmw_cyclonedds_cpp ROS_LOCALHOST_ONLY: 0 run: | - refreshenv - pixi shell - call c:/dev/${{ matrix.ros_distribution }}/ros2-windows/setup.bat - dotnet test -c Release -p:BuildInParallel=false --no-build --verbosity normal --blame-crash-dump-type mini --blame-hang-dump-type none --blame-hang-timeout 1m + refreshenv && ^ + pixi run --manifest-path "c:/dev/pixi/" ^ + cmd /c "call c:/dev/${{ matrix.ros_distribution }}/ros2-windows/setup.bat && dotnet test -c Release -p:BuildInParallel=false --no-build --verbosity normal --blame-crash-dump-type mini --blame-hang-dump-type none --blame-hang-timeout 1m" #test_macos: # runs-on: macos-latest diff --git a/README.md b/README.md index ef2d3fd..2bb7240 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ rclnet is a fast and easy-to-use .NET wrapper over ROS 2 client library, allowin ## What's New in 2.0 - Added support for .NET 10 and changed minimum supported .NET version to 8.0 - - ROS2 Jazzy Support by @AlrayQiu ([#39](https://github.com/noelex/rclnet/pull/39)) + - ROS 2 Kilted Support + - ROS 2 Jazzy Support by @AlrayQiu ([#39](https://github.com/noelex/rclnet/pull/39)) - Simplified message generation workflow by @ha-ves ([#38](https://github.com/noelex/rclnet/pull/38)) - String pooling is now disabled by default @@ -48,6 +49,7 @@ Supported ROS 2 Distributions: - Humble Hawksbill - Iron Irwini - Jazzy Jalisco +- Kilted Kaiju Supported Operating Systems: - Ubuntu diff --git a/src/Rcl.NET.Tests/kilted.Dockerfile b/src/Rcl.NET.Tests/kilted.Dockerfile new file mode 100644 index 0000000..c526d63 --- /dev/null +++ b/src/Rcl.NET.Tests/kilted.Dockerfile @@ -0,0 +1,18 @@ +FROM ros:kilted-ros-core +RUN apt-get update \ + && apt-get -y install --no-install-recommends \ + ros-kilted-rmw-cyclonedds-cpp \ + ros-kilted-rmw-fastrtps-cpp \ + ros-kilted-tf2-msgs \ + ros-kilted-service-msgs \ + wget \ + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* \ + && wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \ + && chmod +x ./dotnet-install.sh \ + && ./dotnet-install.sh --channel 10.0 --runtime dotnet \ + && ./dotnet-install.sh --channel 9.0 --runtime dotnet \ + && ./dotnet-install.sh --channel 8.0 --runtime dotnet +ENV DOTNET_ROOT=/root/.dotnet +ENV PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools diff --git a/src/Rcl.NET.Tests/ubuntu.kilted.cyclonedds.runsettings b/src/Rcl.NET.Tests/ubuntu.kilted.cyclonedds.runsettings new file mode 100644 index 0000000..35e576e --- /dev/null +++ b/src/Rcl.NET.Tests/ubuntu.kilted.cyclonedds.runsettings @@ -0,0 +1,14 @@ + + + + + kilted + 0 + 3 + 2 + /opt/ros/kilted/opt/yaml_cpp_vendor/lib:/opt/ros/kilted/lib/x86_64-linux-gnu:/opt/ros/kilted/lib + /opt/ros/kilted + rmw_cyclonedds_cpp + + + \ No newline at end of file diff --git a/src/Rcl.NET.Tests/ubuntu.kilted.fastrtps.runsettings b/src/Rcl.NET.Tests/ubuntu.kilted.fastrtps.runsettings new file mode 100644 index 0000000..bc65570 --- /dev/null +++ b/src/Rcl.NET.Tests/ubuntu.kilted.fastrtps.runsettings @@ -0,0 +1,14 @@ + + + + + kilted + 0 + 3 + 2 + /opt/ros/kilted/opt/yaml_cpp_vendor/lib:/opt/ros/kilted/lib/x86_64-linux-gnu:/opt/ros/kilted/lib + /opt/ros/kilted + rmw_fastrtps_cpp + + + \ No newline at end of file diff --git a/src/Rcl.NET/Interop/RclJazzy.cs b/src/Rcl.NET/Interop/RclJazzy.cs index a6a05f9..ad344db 100644 --- a/src/Rcl.NET/Interop/RclJazzy.cs +++ b/src/Rcl.NET/Interop/RclJazzy.cs @@ -1,8 +1,16 @@ -using Rosidl.Runtime.Interop; -using System.Runtime.InteropServices; -using static Rcl.Interop.RclHumble; +using System.Runtime.InteropServices; namespace Rcl.Interop; -// Same typedef with iron -using RclJazzy = RclIron; \ No newline at end of file +unsafe static class RclJazzy +{ + [DllImport("rcl", CallingConvention = CallingConvention.Cdecl)] + public static extern rcl_ret_t rcl_timer_init2( + RclCommon.rcl_timer_t* timer, + RclCommon.rcl_clock_t* clock, + RclCommon.rcl_context_t* context, + long period, + delegate* unmanaged[Cdecl] callback, + RclCommon.rcl_allocator_t allocator, + bool autostart); +} \ No newline at end of file diff --git a/src/Rcl.NET/Introspection/MessageIntrospection.cs b/src/Rcl.NET/Introspection/MessageIntrospection.cs index 4f9d1d1..83f834c 100644 --- a/src/Rcl.NET/Introspection/MessageIntrospection.cs +++ b/src/Rcl.NET/Introspection/MessageIntrospection.cs @@ -39,7 +39,7 @@ public unsafe static IMessageIntrospection Create(MessageTypeSupport* typesuppor { return new HumbleMessageIntrospection(typesupport); } - else if (RosEnvironment.IsJazzy) + else if (RosEnvironment.IsJazzy || RosEnvironment.IsKilted) { return new JazzyMessageIntrospection(typesupport); } @@ -62,7 +62,7 @@ public unsafe static IMessageIntrospection Create(MessageMembers* messageMembers { return new HumbleMessageIntrospection(messageMembers); } - else if (RosEnvironment.IsJazzy) + else if (RosEnvironment.IsJazzy || RosEnvironment.IsKilted) { return new JazzyMessageIntrospection((MessageMembers_Jazzy*)messageMembers); } diff --git a/src/Rcl.NET/Parameters/Impl/ParameterService.cs b/src/Rcl.NET/Parameters/Impl/ParameterService.cs index 00bc635..dc33878 100644 --- a/src/Rcl.NET/Parameters/Impl/ParameterService.cs +++ b/src/Rcl.NET/Parameters/Impl/ParameterService.cs @@ -98,7 +98,7 @@ internal unsafe ParameterService(RclNodeImpl node, IDictionary { return &((RclFoxy.rcl_node_options_t*)handle)->arguments; } - else if (RosEnvironment.IsHumble || RosEnvironment.IsIron || RosEnvironment.IsJazzy) + else if (RosEnvironment.IsHumble || RosEnvironment.IsIron || RosEnvironment.IsJazzy || RosEnvironment.IsKilted) { return &((RclHumble.rcl_node_options_t*)handle)->arguments; } diff --git a/src/Rcl.NET/RosEnvironment.cs b/src/Rcl.NET/RosEnvironment.cs index 541944d..3f7a16f 100644 --- a/src/Rcl.NET/RosEnvironment.cs +++ b/src/Rcl.NET/RosEnvironment.cs @@ -14,7 +14,7 @@ internal enum VersionRequirement /// public unsafe static class RosEnvironment { - private static readonly string[] SupportedDistributions = new[] { Foxy, Humble, Iron, Jazzy }; + private static readonly string[] SupportedDistributions = [Foxy, Humble, Iron, Jazzy, Kilted]; /// /// ROS 2 Foxy Fitzroy. @@ -36,6 +36,11 @@ public unsafe static class RosEnvironment /// public const string Jazzy = "jazzy"; + /// + /// ROS 2 Kilted Kaiju + /// + public const string Kilted = "kilted"; + /// /// Gets whether the application is running in foxy. /// @@ -50,11 +55,17 @@ public unsafe static class RosEnvironment /// Gets whether the application is running in iron. /// public static bool IsIron => Distribution == Iron; + /// - /// Gets whether the application is running in iron. + /// Gets whether the application is running in jazzy. /// public static bool IsJazzy => Distribution == Jazzy; + /// + /// Gets whether the application is running in kilted. + /// + public static bool IsKilted => Distribution == Kilted; + /// /// Get the name of the rmw implementation being used. /// diff --git a/src/Rcl.NET/SafeHandles/SafeNodeHandle.cs b/src/Rcl.NET/SafeHandles/SafeNodeHandle.cs index a51a372..d5bc840 100644 --- a/src/Rcl.NET/SafeHandles/SafeNodeHandle.cs +++ b/src/Rcl.NET/SafeHandles/SafeNodeHandle.cs @@ -31,6 +31,7 @@ public SafeNodeHandle(SafeContextHandle context, string name, string @namespace, case RosEnvironment.Humble: case RosEnvironment.Iron: case RosEnvironment.Jazzy: + case RosEnvironment.Kilted: InitHumbleOrLater(namePtr, nsPtr, context, options); break; default: throw new NotImplementedException(); diff --git a/src/Rcl.NET/SafeHandles/SafeTimerHandle.cs b/src/Rcl.NET/SafeHandles/SafeTimerHandle.cs index 3fa3fb0..fe3fb8e 100644 --- a/src/Rcl.NET/SafeHandles/SafeTimerHandle.cs +++ b/src/Rcl.NET/SafeHandles/SafeTimerHandle.cs @@ -1,4 +1,4 @@ -using Rosidl.Messages.Rosgraph; +using Rcl.Interop; namespace Rcl.SafeHandles; @@ -10,15 +10,24 @@ public SafeTimerHandle( SafeContextHandle context, SafeClockHandle clock, long period) { _clock = clock; - * Object = rcl_get_zero_initialized_timer(); + *Object = rcl_get_zero_initialized_timer(); try { using (ScopedLock.Lock(ref _clock.SyncRoot)) { - RclException.ThrowIfNonSuccess( - rcl_timer_init(Object, clock.Object, context.Object, - period, null, RclAllocator.Default.Object)); + if (RosEnvironment.IsSupported(RosEnvironment.Jazzy)) + { + RclException.ThrowIfNonSuccess( + RclJazzy.rcl_timer_init2(Object, clock.Object, context.Object, + period, null, RclAllocator.Default.Object, true)); + } + else + { + RclException.ThrowIfNonSuccess( + rcl_timer_init(Object, clock.Object, context.Object, + period, null, RclAllocator.Default.Object)); + } _clock.AddTimerRef(); } } diff --git a/src/testEnvironments.json b/src/testEnvironments.json index bf79059..072df2d 100644 --- a/src/testEnvironments.json +++ b/src/testEnvironments.json @@ -35,6 +35,11 @@ "name": "Container - Jazzy", "type": "docker", "dockerFile": "Rcl.NET.Tests/jazzy.Dockerfile" + }, + { + "name": "Container - Kilted", + "type": "docker", + "dockerFile": "Rcl.NET.Tests/kilted.Dockerfile" } ] }