diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c895791..bc0d5a9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -140,6 +140,8 @@ jobs:
package:
needs: [build-native, test]
runs-on: ubuntu-latest
+ outputs:
+ version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
@@ -183,8 +185,93 @@ jobs:
name: nuget-package
path: artifacts/*.nupkg
+ # Smoke test the built NuGet package on every supported RID.
+ #
+ # This is NOT a rebuild of the solution - it consumes the .nupkg produced
+ # by the `package` job via a PackageReference, then runs a tiny console
+ # app that exercises the native FFI path. The purpose is to catch
+ # packaging-level regressions that project-reference tests miss:
+ # * missing runtimes/{rid}/native/* entries in the .nupkg
+ # * wrong architecture binaries shipped for a RID
+ # * DllImport resolver failing to find the native library
+ # when the package is consumed as a NuGet dependency
+ package-smoke-test:
+ needs: package
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - os: windows-latest
+ rid: win-x64
+ dotnet-arch: x64
+ - os: windows-11-arm
+ rid: win-arm64
+ dotnet-arch: arm64
+ - os: ubuntu-latest
+ rid: linux-x64
+ dotnet-arch: x64
+ - os: ubuntu-24.04-arm
+ rid: linux-arm64
+ dotnet-arch: arm64
+ # No Intel macOS runners remain; run the x64 SDK under Rosetta
+ # on an ARM64 runner instead.
+ - os: macos-latest
+ rid: osx-x64
+ dotnet-arch: x64
+ - os: macos-latest
+ rid: osx-arm64
+ dotnet-arch: arm64
+ runs-on: ${{ matrix.os }}
+ env:
+ # Force an isolated package cache so we always consume the freshly
+ # built .nupkg, never a cached earlier version.
+ NUGET_PACKAGES: ${{ github.workspace }}/.smoke-nuget-cache
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+ steps:
+ - uses: actions/checkout@v4
+ # No submodules needed - the smoke test project has zero
+ # dependencies on the vendored rerun source tree.
+
+ - name: Setup .NET (${{ matrix.dotnet-arch }})
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '8.0.x'
+ architecture: ${{ matrix.dotnet-arch }}
+
+ - name: Download built NuGet package
+ uses: actions/download-artifact@v4
+ with:
+ name: nuget-package
+ path: artifacts
+
+ - name: List downloaded package
+ shell: bash
+ run: ls -la artifacts
+
+ - name: Restore smoke test against local package
+ shell: bash
+ working-directory: tests/Rerun.Net.PackageSmokeTest
+ run: |
+ dotnet restore \
+ -p:RerunNetVersion=${{ needs.package.outputs.version }}
+
+ - name: Build smoke test
+ shell: bash
+ working-directory: tests/Rerun.Net.PackageSmokeTest
+ run: |
+ dotnet build --no-restore -c Release \
+ -p:RerunNetVersion=${{ needs.package.outputs.version }}
+
+ - name: Run smoke test (${{ matrix.rid }})
+ shell: bash
+ working-directory: tests/Rerun.Net.PackageSmokeTest
+ run: |
+ dotnet run --no-build -c Release \
+ -p:RerunNetVersion=${{ needs.package.outputs.version }}
+
deploy:
- needs: [package, codegen-check]
+ needs: [package, package-smoke-test, codegen-check]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
diff --git a/tests/Rerun.Net.PackageSmokeTest/NuGet.config b/tests/Rerun.Net.PackageSmokeTest/NuGet.config
new file mode 100644
index 0000000..bff5658
--- /dev/null
+++ b/tests/Rerun.Net.PackageSmokeTest/NuGet.config
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
diff --git a/tests/Rerun.Net.PackageSmokeTest/Program.cs b/tests/Rerun.Net.PackageSmokeTest/Program.cs
new file mode 100644
index 0000000..183aa80
--- /dev/null
+++ b/tests/Rerun.Net.PackageSmokeTest/Program.cs
@@ -0,0 +1,90 @@
+// Package smoke test for Rerun.Net.
+//
+// Consumes the published Rerun.Net NuGet package and exercises the full
+// managed -> native code path on whatever RID this process happens to be
+// running on. The goal is to prove, per platform, that:
+//
+// 1. The package restores cleanly from a PackageReference.
+// 2. The runtimes/{rid}/native/{libname} binary shipped inside the .nupkg
+// is discovered and loaded by the custom DllImportResolver / .NET host.
+// 3. A round-trip RecordingStream -> Save -> disposed flow actually
+// produces a non-empty .rrd file.
+//
+// Exits 0 on success, non-zero on failure. Any exception is printed to
+// stderr so the CI log makes the failure obvious.
+
+using System.Runtime.InteropServices;
+using Rerun.Net;
+using Rerun.Net.Archetypes;
+using Rerun.Net.Components;
+using Rerun.Net.Datatypes;
+
+Console.WriteLine("=== Rerun.Net package smoke test ===");
+Console.WriteLine($"RuntimeIdentifier : {RuntimeInformation.RuntimeIdentifier}");
+Console.WriteLine($"OSDescription : {RuntimeInformation.OSDescription}");
+Console.WriteLine($"OSArchitecture : {RuntimeInformation.OSArchitecture}");
+Console.WriteLine($"ProcessArch : {RuntimeInformation.ProcessArchitecture}");
+Console.WriteLine($"FrameworkDesc : {RuntimeInformation.FrameworkDescription}");
+
+string? rrdPath = null;
+try
+{
+ // Step 1: touch the native library via the version entry point. This is
+ // the cheapest call that forces rerun_c to load; if the native binary
+ // for this RID is missing or the wrong architecture, we fail here with
+ // a DllNotFoundException or BadImageFormatException before touching
+ // anything else.
+ var version = RecordingStream.VersionString();
+ Console.WriteLine($"rerun_c version : {version}");
+ if (string.IsNullOrWhiteSpace(version))
+ throw new InvalidOperationException("rr_version_string returned an empty string.");
+
+ // Step 2: exercise the full RecordingStream -> Save -> Log -> Dispose
+ // path to prove Arrow FFI marshalling and sink wiring work in the
+ // packaged build, not just for version strings.
+ rrdPath = Path.Combine(
+ Path.GetTempPath(),
+ $"rerun_net_pkg_smoke_{Guid.NewGuid():N}.rrd");
+
+ using (var rec = new RecordingStream("rerun_net_package_smoke_test"))
+ {
+ rec.Save(rrdPath);
+ rec.SetTimeSequence("frame", 0);
+
+ var points = new Points3D(
+ new Position3D(new Vec3D([0f, 0f, 0f])),
+ new Position3D(new Vec3D([1f, 1f, 1f])),
+ new Position3D(new Vec3D([2f, 2f, 2f])))
+ .WithColors(
+ new Color(new Rgba32(0xFF0000FFu)),
+ new Color(new Rgba32(0x00FF00FFu)),
+ new Color(new Rgba32(0x0000FFFFu)))
+ .WithRadii(new Radius(0.5f));
+
+ rec.Log("smoke/points", points);
+ }
+
+ var info = new FileInfo(rrdPath);
+ if (!info.Exists)
+ throw new InvalidOperationException($"RRD file was not created at {rrdPath}.");
+ if (info.Length == 0)
+ throw new InvalidOperationException($"RRD file at {rrdPath} is empty.");
+
+ Console.WriteLine($"Wrote {info.Length} bytes to {rrdPath}");
+ Console.WriteLine("=== SMOKE TEST OK ===");
+ return 0;
+}
+catch (Exception ex)
+{
+ Console.Error.WriteLine("=== SMOKE TEST FAILED ===");
+ Console.Error.WriteLine(ex);
+ return 1;
+}
+finally
+{
+ if (rrdPath is not null && File.Exists(rrdPath))
+ {
+ try { File.Delete(rrdPath); }
+ catch { /* best-effort cleanup */ }
+ }
+}
diff --git a/tests/Rerun.Net.PackageSmokeTest/Rerun.Net.PackageSmokeTest.csproj b/tests/Rerun.Net.PackageSmokeTest/Rerun.Net.PackageSmokeTest.csproj
new file mode 100644
index 0000000..47dea86
--- /dev/null
+++ b/tests/Rerun.Net.PackageSmokeTest/Rerun.Net.PackageSmokeTest.csproj
@@ -0,0 +1,31 @@
+
+
+
+
+
+ Exe
+ Rerun.Net.PackageSmokeTest
+ Rerun.Net.PackageSmokeTest
+ false
+
+ 0.0.0-ci-local
+
+
+
+
+
+
+