Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 75 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,70 @@ on:
workflow_dispatch:

jobs:
# ---------------------------------------------------------------------------
# Linux: build the native CycloneDDS libraries + idlc, then build and test the
# managed code against them. Building the managed solution exercises the
# build-time IDL code generator (idlc) on Linux end-to-end, and the runtime
# tests exercise the DllImport("ddsc") -> libddsc.so path. The native artifacts
# are uploaded for the Windows pack job to fold into the cross-platform package.
# ---------------------------------------------------------------------------
linux-build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Required for Nerdbank.GitVersioning to determine version
submodules: recursive

- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
# net8.0 is the target framework, but the code uses C# 13 language
# features (e.g. ref structs in async methods), which require the .NET 10
# SDK's compiler. Install both: the 10.0 SDK builds, the 8.0 runtime runs
# the net8.0 test assemblies.
dotnet-version: |
8.0.x
10.0.x

- name: Install native build prerequisites
run: sudo apt-get update && sudo apt-get install -y cmake build-essential patchelf

- name: Display Tools Versions
run: |
dotnet --version
cmake --version
gcc --version

- name: Build Native (linux-x64)
shell: bash
run: build/native-linux.sh Release

- name: Build Managed Code
run: dotnet build CycloneDDS.NET.Core.slnf -c Release

- name: Run Runtime Tests
run: dotnet test tests/CycloneDDS.Runtime.Tests/CycloneDDS.Runtime.Tests.csproj -c Release --no-build --logger "console;verbosity=normal"

- name: Upload linux-x64 Native Artifacts
uses: actions/upload-artifact@v4
with:
name: native-linux-x64
path: artifacts/native/linux-x64/*
if-no-files-found: error

# ---------------------------------------------------------------------------
# Windows: full build, test and pack via the existing pack.ps1 pipeline
# (builds win-x64 native, builds core, runs runtime tests, packs both the
# CycloneDDS.NET and DdsMonitor packages, builds the examples). The linux-x64
# native artifacts produced above are downloaded first so the packed NuGet
# carries both win-x64 and linux-x64 runtime assets.
# ---------------------------------------------------------------------------
build:
runs-on: windows-latest

needs: [ linux-build ]

env:
# This ensures the build script can find CMake if not in default path (though it usually is on runners)
CMAKE_GENERATOR: "Visual Studio 17 2022"
Expand All @@ -25,13 +86,25 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
# net8.0 is the target framework, but the code uses C# 13 language
# features (e.g. ref structs in async methods), which require the .NET 10
# SDK's compiler. Install both: the 10.0 SDK builds, the 8.0 runtime runs
# the net8.0 test assemblies.
dotnet-version: |
8.0.x
10.0.x

- name: Display Tools Versions
run: |
dotnet --version
cmake --version

- name: Download linux-x64 Native Artifacts
uses: actions/download-artifact@v4
with:
name: native-linux-x64
path: artifacts/native/linux-x64/

- name: Build, Test, and Pack
shell: pwsh
run: .\build\pack.ps1
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ nunit-*.xml
cyclone-compiled/
cyclone-compiled-debug/
build/native/
build/native-linux/
test-extract/
test-output/
**/Generated/*
Expand Down
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## unreleased
nothing yet

## 0.3.1

### Added
- **Linux x64 support.** The package now ships native assets for `linux-x64`
(`libddsc.so`) alongside `win-x64`, and bundles the Linux `idlc` plus its
`.so` dependencies under `tools/`. A new `build/native-linux.sh` compiles the
native CycloneDDS libraries and `idlc` and rewrites their RPATH to `$ORIGIN`
so they resolve in the flat NuGet `tools/` layout. `IdlcRunner` is now
platform-aware — it locates `idlc`/`idlc.exe` under the correct RID, sets
`LD_LIBRARY_PATH`, and restores the Unix execute bit that NuGet does not
preserve — and the MSBuild native-asset copies are guarded by
`IsOSPlatform`. The target framework remains `net8.0`.
- **`ddsmonitor` global tool runs on Linux.** Its tool payload now bundles both
the win-x64 (`ddsc.dll`) and linux-x64 (`libddsc.so`) native runtime, so
`dotnet tool install -g CycloneDDS.NET.DdsMonitor` works on either OS.
Previously the tool carried only the pack host's native and would fail with a
`DllNotFoundException` on the other platform. The bundled `IdlImporter` also
works on Linux via the platform-aware `idlc` shipped in the package's `tools/`.

### Fixed
- **DDS matched-status events never fired on Linux.** The publication- and
subscription-matched listener callbacks passed their 24-byte status struct by
`ref`, which matches the Windows x64 ABI but not the Linux System V ABI (where
structs larger than 16 bytes are passed by value on the stack), so `arg` was
read from the wrong register and every callback was silently dropped. The
status is now passed by value, letting the marshaller emit the correct ABI on
both platforms, so `PublicationMatched`, `SubscriptionMatched` and
`WaitForReaderAsync` work on Linux.
- **Mis-cased project references.** Several `ProjectReference` paths used
`..\..\Src\...` (capital `S`), which resolved only on case-insensitive
(Windows) filesystems; corrected to `src` so the solution builds on Linux.

### Changed
- CI builds native libraries on both Windows and Linux and produces a single
cross-platform package. It installs both the .NET 8 and .NET 10 SDKs: the code
uses C# 13 language features that require the newer compiler, while the target
framework stays `net8.0`.
- Package description updated from "Win64" to "Windows x64, Linux x64".

## 0.2.3

### Fixed
Expand Down
36 changes: 24 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@ dotnet add package CycloneDDS.NET
```

This single package includes:
- **Runtime Library:** High-performance managed bindings.
- **Native Assets:** Pre-compiled `ddsc.dll` and `idlc.exe` (Windows x64).
- **Runtime Library:** High-performance managed bindings (targets `net8.0`; runs on .NET 8 and later).
- **Native Assets:** Pre-compiled native runtime and IDL compiler for both **Windows x64** (`ddsc.dll`, `idlc.exe`) and **Linux x64** (`libddsc.so`, `idlc`). NuGet selects the correct runtime asset per platform automatically.
- **Build Tools:** Automatic C# code generation during build.

**Important:** This package relies on native libraries that require the [Visual C++ Redistributable for Visual Studio 2022](https://aka.ms/vs/17/release/vc_redist.x64.exe) to be installed on the target system.

### Supported platforms

| Platform | Native runtime | Target requirement |
| :--- | :--- | :--- |
| Windows x64 | `ddsc.dll` | [Visual C++ Redistributable for Visual Studio 2022](https://aka.ms/vs/17/release/vc_redist.x64.exe) installed on the target machine. |
| Linux x64 | `libddsc.so` | A glibc-based distribution (e.g. Ubuntu/Debian). No extra runtime install needed. |

### Working with Source Code

Expand All @@ -35,16 +40,22 @@ If you want to build the project from source or contribute:
cd CycloneDds.NET
```

2. **Build and Test** (One-Stop Script):
Run the developer workflow script. This will automatically check for native artifacts (building them if missing), build the solution, and run all tests.
```powershell
.\build\build-and-test.ps1
2. **Build the native libraries** for your platform:
- **Windows** (PowerShell): `.\build\native-win.ps1`
- **Linux** (bash): `build/native-linux.sh Release`

Then build and test the managed solution:
```bash
dotnet build CycloneDDS.NET.Core.slnf -c Release
dotnet test CycloneDDS.NET.Core.slnf -c Release
```
On Windows you can instead run the one-stop script `.\build\build-and-test.ps1`, which builds the native artifacts (if missing), builds the solution, and runs the tests.

3. **Requirements:**
- .NET 8.0 SDK
- Visual Studio 2022 (C++ Desktop Development workload) for native compilation.
- CMake 3.16+ in your PATH.
- **.NET SDK:** the solution targets `net8.0`, but building requires the **.NET 10 SDK** (the code uses C# 13 language features). Produced binaries still run on .NET 8+.
- **CMake 3.16+** on your `PATH`.
- **Windows:** Visual Studio 2022 with the *C++ Desktop Development* workload (for native compilation).
- **Linux:** a C toolchain and `patchelf` — e.g. `sudo apt-get install -y cmake build-essential patchelf`.

## Key Features

Expand Down Expand Up @@ -558,7 +569,8 @@ To run it:
The `CycloneDDS.NET` package bundles these internal components:

* **Managed Libraries:** `CycloneDDS.Core`, `CycloneDDS.Schema`, `CycloneDDS.CodeGen`, `CycloneDDS.Runtime`
* **Native Assets:** `ddsc.dll` (Cyclone DDS), `idlc.exe` (IDL Compiler), `cycloneddsidljson.dll` (IDL JSON plugin)
* **Native Assets (Windows x64):** `ddsc.dll` (Cyclone DDS), `idlc.exe` (IDL Compiler), `cycloneddsidljson.dll` (IDL JSON plugin)
* **Native Assets (Linux x64):** `libddsc.so` (Cyclone DDS), `idlc` (IDL Compiler), `libcycloneddsidljson.so` (IDL JSON plugin)

## Performance Characteristics

Expand Down
150 changes: 150 additions & 0 deletions build/native-linux.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/usr/bin/env bash
set -euo pipefail

# ===================================================================================
# build/native-linux.sh
#
# PURPOSE:
# Compiles the native (C/C++) Cyclone DDS submodule for Linux and copies the
# resulting binaries (.so libraries + idlc) to the local 'artifacts' directory.
# This is a prerequisite for running managed tests or packing the NuGet package
# with linux-x64 support. It is the Linux counterpart of build/native-win.ps1.
#
# USAGE:
# ./build/native-linux.sh [Release|Debug]
# Default: Release
# ===================================================================================

CONFIG="${1:-Release}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
SOURCE_DIR="$REPO_ROOT/cyclonedds"
BUILD_DIR="$REPO_ROOT/build/native-linux"
INSTALL_DIR="$REPO_ROOT/artifacts/native-install-linux"
ARTIFACTS_DIR="$REPO_ROOT/artifacts/native/linux-x64"

echo "============================================================"
echo " Building Native CycloneDDS for Linux ($CONFIG)"
echo "============================================================"

# ----------------------------------------------------------------
# Prerequisites
# ----------------------------------------------------------------
if ! command -v cmake &> /dev/null; then
echo "ERROR: cmake is not installed or not in PATH." >&2
exit 1
fi

if ! command -v gcc &> /dev/null && ! command -v cc &> /dev/null; then
echo "ERROR: no C compiler (gcc/cc) found in PATH." >&2
exit 1
fi

if [ ! -d "$SOURCE_DIR" ] || [ -z "$(ls -A "$SOURCE_DIR" 2>/dev/null)" ]; then
echo "ERROR: Native source directory is missing or empty: $SOURCE_DIR" >&2
echo " Run: git submodule update --init --recursive" >&2
exit 1
fi

mkdir -p "$BUILD_DIR" "$INSTALL_DIR" "$ARTIFACTS_DIR"

# ----------------------------------------------------------------
# [1/3] CMake Configure
# ----------------------------------------------------------------
echo ""
echo "[1/3] Configuring CMake..."

cmake -S "$SOURCE_DIR" -B "$BUILD_DIR" \
-DCMAKE_INSTALL_PREFIX="$INSTALL_DIR" \
-DCMAKE_BUILD_TYPE="$CONFIG" \
-DBUILD_IDLC=ON \
-DBUILD_TESTING=OFF \
-DBUILD_EXAMPLES=OFF \
-DENABLE_SSL=OFF \
-DENABLE_SHM=OFF \
-DENABLE_SECURITY=OFF

# ----------------------------------------------------------------
# [2/3] Build & Install
# ----------------------------------------------------------------
echo ""
echo "[2/3] Building & Installing..."

NPROC="${NPROC:-$(nproc 2>/dev/null || echo 4)}"
cmake --build "$BUILD_DIR" --config "$CONFIG" -j "$NPROC"
cmake --install "$BUILD_DIR" --config "$CONFIG"

# ----------------------------------------------------------------
# [3/3] Copy Artifacts & Fix RPATH
# ----------------------------------------------------------------
echo ""
echo "[3/3] Copying artifacts to $ARTIFACTS_DIR..."

LIB_DIR="$INSTALL_DIR/lib"
BIN_DIR="$INSTALL_DIR/bin"
# Some distros install shared libs to lib64.
[ -d "$LIB_DIR" ] || LIB_DIR="$INSTALL_DIR/lib64"

# Copy a shared library, resolving the SONAME symlink chain to the real file so
# we don't have to hard-code the version suffix. The file is staged as both
# <base>.so (linker/convention name) and <base>.so.0 (the runtime SONAME).
copy_lib() {
local base="$1"
local real=""
if [ -e "$LIB_DIR/${base}.so" ]; then
real="$(readlink -f "$LIB_DIR/${base}.so")"
elif [ -e "$LIB_DIR/${base}.so.0" ]; then
real="$(readlink -f "$LIB_DIR/${base}.so.0")"
else
# Last resort: pick the most specific versioned file available.
real="$(ls -1 "$LIB_DIR/${base}.so".* 2>/dev/null | sort | tail -n1 || true)"
fi

if [ -z "$real" ] || [ ! -e "$real" ]; then
echo " [-] Missing ${base}.so* in $LIB_DIR" >&2
return 1
fi

cp -f "$real" "$ARTIFACTS_DIR/${base}.so"
cp -f "$real" "$ARTIFACTS_DIR/${base}.so.0"
echo " [+] ${base}.so / ${base}.so.0"
}

# Runtime library and IDL-compiler support libraries.
copy_lib libddsc
copy_lib libcycloneddsidl
copy_lib libcycloneddsidlc
copy_lib libcycloneddsidljson

# IDL compiler executable.
if [ -f "$BIN_DIR/idlc" ]; then
cp -f "$BIN_DIR/idlc" "$ARTIFACTS_DIR/"
chmod +x "$ARTIFACTS_DIR/idlc"
echo " [+] idlc"
else
echo " [-] Missing idlc in $BIN_DIR" >&2
exit 1
fi

# Fix RPATH: CMake sets RPATH to $ORIGIN/../lib (bin/ -> lib/), but in the flat
# NuGet tools/ directory every file sits side by side. Rewrite RPATH to $ORIGIN/
# so the dynamic linker finds the .so dependencies next to the executable.
if command -v patchelf &> /dev/null; then
echo ""
echo " [+] Fixing RPATH to \$ORIGIN/..."
chmod +w "$ARTIFACTS_DIR/"*.so* "$ARTIFACTS_DIR/idlc" 2>/dev/null || true
for f in "$ARTIFACTS_DIR/"*.so* "$ARTIFACTS_DIR/idlc"; do
[ -e "$f" ] || continue
patchelf --set-rpath '$ORIGIN/' "$f" 2>/dev/null || true
done
echo " [+] RPATH fixed."
else
echo " [!] patchelf not found. Run: sudo apt-get install patchelf"
echo " [!] Without patchelf, LD_LIBRARY_PATH must point at the idlc directory at runtime."
echo " [!] (IdlcRunner sets this automatically; DllImport('ddsc') resolves via the co-located .so.)"
fi

echo ""
echo "Native build complete."
echo "Artifacts staged at: $ARTIFACTS_DIR"
ls -la "$ARTIFACTS_DIR"
6 changes: 5 additions & 1 deletion examples/FeatureDemo/FeatureDemo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@

<!-- Copy native DLLs to output directory -->
<ItemGroup>
<None Include="..\..\artifacts\native\win-x64\*.dll">
<None Include="..\..\artifacts\native\win-x64\*.dll" Condition="$([MSBuild]::IsOSPlatform('Windows'))">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>%(Filename)%(Extension)</Link>
</None>
<None Include="..\..\artifacts\native\linux-x64\**\*" Condition="$([MSBuild]::IsOSPlatform('Linux'))">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>%(Filename)%(Extension)</Link>
</None>
Expand Down
9 changes: 8 additions & 1 deletion src/CycloneDDS.Core/CycloneDDS.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('Windows'))">
<Content Include="..\..\artifacts\native\win-x64\**\*.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
Expand All @@ -19,4 +19,11 @@
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Content>
</ItemGroup>

<ItemGroup Condition="$([MSBuild]::IsOSPlatform('Linux'))">
<Content Include="..\..\artifacts\native\linux-x64\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Content>
</ItemGroup>
</Project>
Loading
Loading