diff --git a/NetCord.Natives/NetCord.Natives.targets b/NetCord.Natives/NetCord.Natives.targets
index 0e8c8d0f..bdb1a6f6 100644
--- a/NetCord.Natives/NetCord.Natives.targets
+++ b/NetCord.Natives/NetCord.Natives.targets
@@ -1,23 +1,40 @@
-
+
- <_NetCordStaticLink Include="libdave;mlspp;bytes;tls_syntax;hpke;libcrypto"
- Condition="'%(DirectPInvoke.Identity)' == 'libdave'" />
- <_NetCordStaticLink Include="libsodium"
+
+
+ <_NetCordStaticLink Include="libsodium;sodium"
Condition="'%(DirectPInvoke.Identity)' == 'libsodium'" />
- <_NetCordStaticLink Include="opus"
- Condition="'%(DirectPInvoke.Identity)' == 'opus'" />
- <_NetCordStaticLink Include="zstd"
- Condition="'%(DirectPInvoke.Identity)' == 'zstd'" />
+ <_NetCordStaticLink Include="libdave;dave"
+ Condition="'%(DirectPInvoke.Identity)' == 'libdave'" />
+
+
+ <_NetCordStaticLink Include="mlspp;hpke;bytes;tls_syntax"
+ Condition="'%(DirectPInvoke.Identity)' == 'libdave'" />
+
+
+ <_NetCordStaticLink Include="opus;zstd"
+ Condition="'%(DirectPInvoke.Identity)' == 'opus' or '%(DirectPInvoke.Identity)' == 'zstd'" />
+
+
+ <_NetCordStaticLink Include="libcrypto;crypto"
+ Condition="'%(DirectPInvoke.Identity)' == 'libdave'" />
-
+
+
+
+ true
+
true
true
false
+ $(NoWarn);IL2075;IL2026
..\..\..\..\NetCord.Natives
+
+
+
+
+
diff --git a/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs
index bf91f09c..6581e697 100644
--- a/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs
+++ b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs
@@ -1,32 +1,9 @@
-using System;
-using System.Diagnostics;
-using System.IO;
using System.Runtime.InteropServices;
+using NetCord.Natives.Tests;
-internal static partial class NativeProbes
-{
- [LibraryImport("libdave", EntryPoint = "daveMaxSupportedProtocolVersion")]
- internal static partial uint DaveMaxSupportedProtocolVersion();
+Console.WriteLine("NetCord native ahead of time publish app.");
- [LibraryImport("libsodium", EntryPoint = "sodium_init")]
- internal static partial int SodiumInit();
-
- [LibraryImport("opus", EntryPoint = "opus_get_version_string")]
- internal static partial nint OpusGetVersionString();
-
- [LibraryImport("zstd", EntryPoint = "ZSTD_versionNumber")]
- internal static partial uint ZstdVersionNumber();
-}
-
-internal static class Program
-{
- private static void Main()
- {
- Console.WriteLine("NetCord native ahead of time publish app.");
-
- Console.WriteLine($"Dave Max Supported Protocol Version: {NativeProbes.DaveMaxSupportedProtocolVersion()}");
- Console.WriteLine($"Sodium Init: {NativeProbes.SodiumInit()}. 0 = OK");
- Console.WriteLine($"Opus Version String: {Marshal.PtrToStringAnsi(NativeProbes.OpusGetVersionString())}");
- Console.WriteLine($"Zstd Version Number: {NativeProbes.ZstdVersionNumber()}");
- }
-}
+Console.WriteLine($"Dave Max Supported Protocol Version: {NativeProbes.DaveMaxSupportedProtocolVersion()}");
+Console.WriteLine($"Sodium Init: {NativeProbes.SodiumInit()}. 0 = OK");
+Console.WriteLine($"Opus Version String: {Marshal.PtrToStringAnsi(NativeProbes.OpusGetVersionString())}");
+Console.WriteLine($"Zstd Version Number: {NativeProbes.ZstdVersionNumber()}");
diff --git a/Tests/NetCord.Natives.Tests/NativeProbes.cs b/Tests/NetCord.Natives.Tests/NativeProbes.cs
new file mode 100644
index 00000000..c8e95ef7
--- /dev/null
+++ b/Tests/NetCord.Natives.Tests/NativeProbes.cs
@@ -0,0 +1,78 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace NetCord.Natives.Tests;
+
+public static partial class NativeProbes
+{
+ [LibraryImport("libdave", EntryPoint = "daveMaxSupportedProtocolVersion")]
+ internal static partial uint DaveMaxSupportedProtocolVersion();
+
+ [LibraryImport("libsodium", EntryPoint = "sodium_init")]
+ internal static partial int SodiumInit();
+
+ [LibraryImport("opus", EntryPoint = "opus_get_version_string")]
+ internal static partial IntPtr OpusGetVersionString();
+
+ [LibraryImport("zstd", EntryPoint = "ZSTD_versionNumber")]
+ internal static partial uint ZstdVersionNumber();
+
+ internal static IReadOnlyList GetMissingLibraryImports(string libName, string className, Assembly assemblyWithImports)
+ {
+ var typeWithImports = assemblyWithImports.GetType(className, true);
+ var libHandle = GetLoadedNativeModuleHandle(libName);
+ var missingExports = new List();
+
+ var methods = typeWithImports!.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
+ .Select(m => new
+ {
+ MethodName = m.Name,
+ Attr = m.GetCustomAttribute(),
+ })
+ .Where(x => x.Attr != null)
+ .ToArray();
+
+ if (methods.Length == 0)
+ throw new InvalidOperationException($"No methods with [LibraryImport] found in '{className}'.");
+
+ foreach (var item in methods)
+ {
+ // Use EntryPoint if defined, otherwise fallback to method name.
+ string exportName = item.Attr!.EntryPoint ?? item.MethodName;
+
+ if (!NativeLibrary.TryGetExport(libHandle, exportName, out _))
+ {
+ missingExports.Add(exportName);
+ }
+ }
+
+ return missingExports;
+ }
+
+ internal static IntPtr GetLoadedNativeModuleHandle(string libName)
+ {
+ _ = libName switch
+ {
+ "libdave" => (object)DaveMaxSupportedProtocolVersion(),
+ "libsodium" => (object)SodiumInit(),
+ "opus" => (object)OpusGetVersionString(),
+ "zstd" => (object)ZstdVersionNumber(),
+ _ => throw new InvalidOperationException($"Unknown library name '{libName}' provided to test."),
+ };
+
+ var module = System.Diagnostics.Process.GetCurrentProcess().Modules
+ .Cast()
+ .FirstOrDefault(m =>
+ {
+ var moduleName = Path.GetFileName(m.ModuleName);
+ return moduleName.StartsWith(libName, StringComparison.OrdinalIgnoreCase)
+ || moduleName.StartsWith($"lib{libName}", StringComparison.OrdinalIgnoreCase);
+ })
+ ?? throw new InvalidOperationException($"Native module '{libName}' was not found in the current process.");
+
+ if (NativeLibrary.TryLoad(module.FileName, out var libHandleByPath))
+ return libHandleByPath;
+
+ throw new InvalidOperationException($"Failed to obtain a handle for native module '{libName}'.");
+ }
+}
diff --git a/Tests/NetCord.Natives.Tests/NativesBuildTests.cs b/Tests/NetCord.Natives.Tests/NativesBuildTests.cs
index f0bc87c6..f2462585 100644
--- a/Tests/NetCord.Natives.Tests/NativesBuildTests.cs
+++ b/Tests/NetCord.Natives.Tests/NativesBuildTests.cs
@@ -1,4 +1,4 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
@@ -7,9 +7,6 @@ namespace NetCord.Natives.Tests;
[TestClass]
public class NativesBuildTests
{
- // MSTest automatically injects this property
- public TestContext TestContext { get; set; }
-
[TestMethod]
[DataRow("libdave")]
[DataRow("libsodium")]
@@ -17,14 +14,14 @@ public class NativesBuildTests
[DataRow("zstd")]
public void NativesLoaded(string libName)
{
- try
+ _ = libName switch
{
- NativeLibrary.Load(libName);
- }
- catch (Exception ex)
- {
- Assert.Fail($"Failed to load library '{libName}': {ex}");
- }
+ "libdave" => (object)NativeProbes.DaveMaxSupportedProtocolVersion(),
+ "libsodium" => (object)NativeProbes.SodiumInit(),
+ "opus" => (object)NativeProbes.OpusGetVersionString(),
+ "zstd" => (object)NativeProbes.ZstdVersionNumber(),
+ _ => throw new InvalidOperationException($"Unknown library name '{libName}' provided to test."),
+ };
}
[TestMethod]
@@ -34,48 +31,13 @@ public void NativesLoaded(string libName)
[DataRow("zstd", "NetCord.Gateway.Compression.Zstandard")]
public void AllLibraryImportsExistInBinary(string libName, string className)
{
- IntPtr libHandle = IntPtr.Zero;
-
- try
- {
- var assembly = typeof(NetCord.Application).Assembly;
- var typeWithImports = assembly.GetType(className, true);
-
- libHandle = NativeLibrary.Load(libName);
-
- var missingExports = new List();
-
- // Reflect over methods with [LibraryImport]
- var methods = typeWithImports!.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
- .Select(m => new {
- MethodName = m.Name,
- Attr = m.GetCustomAttribute()
- })
- .Where(x => x.Attr != null);
- Assert.IsNotEmpty(methods, $"No methods with [LibraryImport] found in '{className}'.");
+ var missingExports = NativeProbes.GetMissingLibraryImports(libName, className, typeof(NetCord.Application).Assembly);
- foreach (var item in methods)
- {
- // Use EntryPoint if defined, otherwise fallback to Method Name
- string exportName = item.Attr!.EntryPoint ?? item.MethodName;
-
- if (!NativeLibrary.TryGetExport(libHandle, exportName, out _))
- {
- missingExports.Add(exportName);
- }
- }
- Assert.IsEmpty(missingExports, $"The following entry points were not found in '{libName}': {string.Join(", ", missingExports)}");
- }
- catch (Exception ex)
- {
- Assert.Fail($"An error occurred while verifying imports for '{libName}': {ex}");
- }
- finally
- {
- NativeLibrary.Free(libHandle);
- }
+ Assert.IsEmpty(missingExports, $"The following entry points were not found in '{libName}': {string.Join(", ", missingExports)}");
}
+ const string NativeAotAppLogTag = $"[{nameof(NativeAotStaticLinking)}]";
+ [DoNotParallelize]
[TestMethod]
[DataRow("libdave;libsodium;opus;zstd")]
public void NativeAotStaticLinking(string libName)
@@ -84,70 +46,107 @@ public void NativeAotStaticLinking(string libName)
var projectFile = Path.Combine(projectDirectory, "NativeAotApp.csproj");
var generatedProjectFile = Path.Combine(projectDirectory, "NativeAotApp.g.csproj");
- try
+ // get properties to be passed to the NativeAotApp build from AssemblyMetadata attribute
+ var assembly = typeof(NativesBuildTests).Assembly;
+
+ var properties = assembly.GetCustomAttributes()?
+ .FirstOrDefault(a => a.Key == "NativeAotAppProps")?.Value;
+ Assert.IsNotNull(properties, "NativeAotAppProps metadata attribute is not defined.");
+
+ // build asset NativeAotApp with Native AoT enabled
+ var buildProcess = new System.Diagnostics.Process();
+ buildProcess.StartInfo.FileName = "dotnet";
+ buildProcess.StartInfo.ArgumentList.Add("publish");
+ buildProcess.StartInfo.ArgumentList.Add("NativeAotApp.csproj");
+ buildProcess.StartInfo.ArgumentList.Add("-tl:off");
+ buildProcess.StartInfo.ArgumentList.Add("-v:n");
+ buildProcess.StartInfo.ArgumentList.Add($"-p:{properties}");
+
+ Console.WriteLine($"{NativeAotAppLogTag} Building Native AoT app in ({projectDirectory}): 'dotnet {buildProcess.StartInfo.ArgumentList.Aggregate((a, b) => $"{a} {b}")}'");
+
+ buildProcess.StartInfo.WorkingDirectory = projectDirectory;
+ buildProcess.StartInfo.RedirectStandardOutput = true;
+ buildProcess.OutputDataReceived += (sender, e) =>
+ {
+ if (!string.IsNullOrEmpty(e.Data))
+ Console.WriteLine($"{NativeAotAppLogTag} {e.Data}");
+ };
+ buildProcess.StartInfo.RedirectStandardError = true;
+ buildProcess.ErrorDataReceived += (sender, e) =>
+ {
+ if (!string.IsNullOrEmpty(e.Data))
+ Console.WriteLine($"{NativeAotAppLogTag} {e.Data}");
+ };
+ var ok = buildProcess.Start();
+ buildProcess.BeginOutputReadLine();
+ buildProcess.BeginErrorReadLine();
+ buildProcess.WaitForExit();
+
+ Assert.AreEqual(0, buildProcess.ExitCode, $"Native AoT build failed for '{libName}'.");
+
+ // Obtain the generated RunCommand from a build so we launch the same command the SDK would.
+ var getRunCmd = new System.Diagnostics.Process();
+ getRunCmd.StartInfo.FileName = "dotnet";
+ getRunCmd.StartInfo.ArgumentList.Add("build");
+ getRunCmd.StartInfo.ArgumentList.Add("NativeAotApp.csproj");
+ getRunCmd.StartInfo.ArgumentList.Add($"-p:{properties}");
+ getRunCmd.StartInfo.ArgumentList.Add("-t:GetTargetPath");
+ getRunCmd.StartInfo.ArgumentList.Add("-getProperty:PublishDir");
+ getRunCmd.StartInfo.ArgumentList.Add("--no-restore");
+
+ string? runCmdOutput = null;
+
+ getRunCmd.StartInfo.WorkingDirectory = projectDirectory;
+ getRunCmd.StartInfo.RedirectStandardOutput = true;
+ getRunCmd.OutputDataReceived += (sender, e) =>
+ {
+ if (!string.IsNullOrEmpty(e.Data))
+ {
+ Console.WriteLine($"{NativeAotAppLogTag} {e.Data}");
+ runCmdOutput = e.Data.Trim('\r', '\n', '"');
+ }
+ };
+ getRunCmd.StartInfo.RedirectStandardError = true;
+ getRunCmd.ErrorDataReceived += (sender, e) =>
+ {
+ if (!string.IsNullOrEmpty(e.Data))
+ Console.WriteLine($"{NativeAotAppLogTag} {e.Data}");
+ };
+ getRunCmd.Start();
+ getRunCmd.BeginErrorReadLine();
+ getRunCmd.BeginOutputReadLine();
+ getRunCmd.WaitForExit();
+
+ Assert.AreEqual(0, getRunCmd.ExitCode, $"Failed to obtain PublishDir for '{libName}'.");
+ Assert.IsFalse(string.IsNullOrEmpty(runCmdOutput), $"PublishDir is empty for '{libName}'.");
+
+ runCmdOutput = Path.Combine(projectDirectory, runCmdOutput,
+ "NativeAotApp" + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ""));
+
+ // check that the library is running without errors, which indicates that it was statically linked successfully
+ var aotProcess = new System.Diagnostics.Process();
+ aotProcess.StartInfo.FileName = runCmdOutput;
+ aotProcess.StartInfo.WorkingDirectory = Path.GetDirectoryName(runCmdOutput);
+
+ Console.WriteLine($"{NativeAotAppLogTag} Running Native AoT app: '{runCmdOutput}'");
+
+ aotProcess.StartInfo.RedirectStandardOutput = true;
+ aotProcess.OutputDataReceived += (sender, e) =>
{
- var originalProjectFileContents = File.ReadAllText(projectFile);
- var generatedProjectFileContents = originalProjectFileContents.Replace("$(NetCordDirectPInvoke)", libName);
- File.WriteAllText(generatedProjectFile, generatedProjectFileContents);
-
- // get NetCordNativesDir from AssemblyMetadata attribute
- var assembly = typeof(NativesBuildTests).Assembly;
- var nativesDir = assembly.GetCustomAttributes()?
- .FirstOrDefault(a => a.Key == "NetCordNativesDir")?.Value;
- Assert.IsNotNull(nativesDir, "NetCordNativesDir metadata attribute is not defined.");
- var vcpkgRoot = assembly.GetCustomAttributes()?
- .FirstOrDefault(a => a.Key == "VcpkgRoot")?.Value;
- Assert.IsNotNull(vcpkgRoot, "VcpkgRoot metadata attribute is not defined.");
- var targetFramework = assembly.GetCustomAttributes()?
- .FirstOrDefault(a => a.Key == "TargetFramework")?.Value;
- Assert.IsNotNull(targetFramework, "TargetFramework metadata attribute is not defined.");
-
- // build asset NativeAotApp with Native AoT enabled
- var buildProcess = new System.Diagnostics.Process();
- buildProcess.StartInfo.FileName = "dotnet";
- buildProcess.StartInfo.ArgumentList.Add("publish");
- buildProcess.StartInfo.ArgumentList.Add("NativeAotApp.g.csproj");
- buildProcess.StartInfo.ArgumentList.Add("-p:Configuration=Release");
- buildProcess.StartInfo.ArgumentList.Add($"-p:TargetFramework={targetFramework}");
- buildProcess.StartInfo.ArgumentList.Add("-p:RuntimeIdentifier=" + RuntimeInformation.RuntimeIdentifier);
- buildProcess.StartInfo.ArgumentList.Add($"-p:NetCordNativesDir={nativesDir}");
- buildProcess.StartInfo.ArgumentList.Add($"-p:VcpkgRoot={vcpkgRoot}");
-
- TestContext.WriteLine($"Building Native AoT app in ({projectDirectory}): 'dotnet {buildProcess.StartInfo.ArgumentList.Aggregate((a, b) => $"{a} {b}")}'");
-
- buildProcess.StartInfo.WorkingDirectory = projectDirectory;
- buildProcess.StartInfo.RedirectStandardOutput = true;
- buildProcess.StartInfo.RedirectStandardError = true;
- buildProcess.Start();
- buildProcess.WaitForExit();
-
- TestContext.WriteLine($"Build Output of AoT app for '{libName}': {buildProcess.StandardOutput.ReadToEnd()}");
-
- Assert.AreEqual(0, buildProcess.ExitCode,
- $"Native AoT build failed for '{libName}'. Output: {buildProcess.StandardError.ReadToEnd()}");
-
- // check that the library is running without errors, which indicates that it was statically linked successfully
- var aotProcess = new System.Diagnostics.Process();
- aotProcess.StartInfo.FileName = Path.Combine(projectDirectory, "bin", "release", targetFramework,
- RuntimeInformation.RuntimeIdentifier, "publish",
- RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
- "NativeAotApp.g.exe" : "NativeAotApp.g");
-
- TestContext.WriteLine($"Running Native AoT app: '{aotProcess.StartInfo.FileName}'");
-
- aotProcess.StartInfo.RedirectStandardOutput = true;
- aotProcess.StartInfo.RedirectStandardError = true;
- aotProcess.Start();
- aotProcess.WaitForExit();
-
- TestContext.WriteLine($"Output of AoT app for '{libName}': {aotProcess.StandardOutput.ReadToEnd()}");
-
- Assert.AreEqual(0, aotProcess.ExitCode,
- $"Native AoT app failed to run for '{libName}'. Output: {aotProcess.StandardError.ReadToEnd()}");
- }
- catch (Exception ex)
+ if (!string.IsNullOrEmpty(e.Data))
+ Console.WriteLine($"{NativeAotAppLogTag} {e.Data}");
+ };
+ aotProcess.StartInfo.RedirectStandardError = true;
+ aotProcess.ErrorDataReceived += (sender, e) =>
{
- Assert.Fail($"Failed to statically link '{libName}' for Native Ahead-of-Time (AoT) compilation: {ex}");
- }
+ if (!string.IsNullOrEmpty(e.Data))
+ Console.WriteLine($"{NativeAotAppLogTag} {e.Data}");
+ };
+ aotProcess.Start();
+ aotProcess.BeginOutputReadLine();
+ aotProcess.BeginErrorReadLine();
+ aotProcess.WaitForExit();
+
+ Assert.AreEqual(0, aotProcess.ExitCode, $"Native AoT app failed to run for '{libName}'.");
}
}
diff --git a/Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj b/Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj
index 4eaa6e27..6dd95eed 100644
--- a/Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj
+++ b/Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj
@@ -4,6 +4,7 @@
net10.0
enable
enable
+ true
false
true
@@ -13,6 +14,8 @@
+
@@ -23,25 +26,25 @@
+
+
..\..\NetCord.Natives\
+ NetCordNativesDir=$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\$(NetCordNativesDir)'))
+
+ $(VCPKG_ROOT)
+ $(NativeAotAppProps),VcpkgRoot=$(VcpkgRoot)
+
+ $(NativeAotAppProps),$(AppendNativeAotAppProps)
- <_Parameter1>NetCordNativesDir
- <_Parameter2>$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\$(NetCordNativesDir)'))
-
-
- <_Parameter1>VcpkgRoot
- <_Parameter2>$(VcpkgRoot)
-
-
- <_Parameter1>TargetFramework
- <_Parameter2>$(TargetFramework)
+ <_Parameter1>NativeAotAppProps
+ <_Parameter2>$(NativeAotAppProps)
From a748fa61d9c49f4c0e79a76e6163fbcab3870d9d Mon Sep 17 00:00:00 2001
From: Haves Irfan <20160532+ha-ves@users.noreply.github.com>
Date: Mon, 11 May 2026 05:49:25 +0900
Subject: [PATCH 10/10] Clean up & atomize CI workflows * Skip testing for OSX
due to dyld limitation * Clean up natives packaging * Docs updates for
prebuilt natives
---
.github/workflows/build-and-publish.yml | 2 +
.github/workflows/build-natives.yml | 82 +++++------
.github/workflows/build-publish-natives.yml | 35 +++++
.github/workflows/build.yml | 17 +--
Directory.Packages.props | 1 +
.../installing-native-dependencies.md | 138 ++++++++++--------
NetCord.Natives/NativesHelper.cs | 20 ---
NetCord.Natives/NetCord.Natives.csproj | 29 ++--
Tests/NetCord.Natives.Tests/NativeProbes.cs | 20 ++-
.../NativesBuildTests.cs | 3 +
.../NetCord.Natives.Tests.csproj | 1 -
11 files changed, 192 insertions(+), 156 deletions(-)
create mode 100644 .github/workflows/build-publish-natives.yml
delete mode 100644 NetCord.Natives/NativesHelper.cs
diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml
index c9de0054..1bdd8c0f 100644
--- a/.github/workflows/build-and-publish.yml
+++ b/.github/workflows/build-and-publish.yml
@@ -20,6 +20,7 @@ jobs:
with:
name: NuGet Packages
path: artifacts/pkgs
+ merge-multiple: true
- name: Download Documentation Artifacts
uses: actions/download-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
@@ -40,6 +41,7 @@ jobs:
KEY: ${{ secrets.NUGET_API_KEY }}
run: |
dotnet nuget push artifacts/pkgs/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push artifacts/pkgs/*.snupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate
- name: Deploy Documentation
uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345
diff --git a/.github/workflows/build-natives.yml b/.github/workflows/build-natives.yml
index a5079949..6cada1d3 100644
--- a/.github/workflows/build-natives.yml
+++ b/.github/workflows/build-natives.yml
@@ -1,17 +1,20 @@
-name: Build, Test, and Package NetCord.Natives
+name: Build NetCord.Natives
on:
+ workflow_call:
workflow_dispatch:
+ inputs:
+ GenerateBinLog:
+ type: boolean
+ default: false
push:
paths:
- 'NetCord.Natives/**'
+ - 'Tests/NetCord.Natives.Tests/**'
- '.github/workflows/build-natives.yml'
- tags:
- - "[0-9]+.[0-9]+.[0-9]+"
- - "[0-9]+.[0-9]+.[0-9]+-*"
jobs:
- build-test-natives:
+ build-natives:
runs-on: ${{ matrix.runs-on }}
env:
CI: true
@@ -22,6 +25,7 @@ jobs:
Configuration: Release
RuntimeIdentifier: ${{ matrix.rid }}
DotnetVerbose: minimal
+ GenerateBinLog: ${{ github.event.inputs.GenerateBinLog || inputs.GenerateBinLog || false }}
strategy:
fail-fast: false
@@ -57,6 +61,7 @@ jobs:
uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
+ filter: tree:0
- name: Restore vcpkg cache
uses: actions/cache/restore@v5.0.5
@@ -82,60 +87,56 @@ jobs:
shell: bash
run: |
brew update
- brew install autoconf automake libtool
+ for pkg in autoconf automake libtool; do
+ brew list $pkg &>/dev/null || brew install $pkg
+ done
- name: Restore
shell: pwsh
run: |
- dotnet restore Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj -v normal
+ dotnet restore Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj -v ${{ env.DotnetVerbose }} ${{ env.GenerateBinLog == 'true' && '-bl:restore.binlog' || '' }}
- name: Build natives projects for ${{ matrix.rid }}
shell: pwsh
run: > # Building tests will build dependencies including the native libraries
dotnet build Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj --no-restore -v ${{ env.DotnetVerbose }}
+ ${{ env.GenerateBinLog == 'true' && '-bl:build.binlog' || '' }}
-p:AppendNativeAotAppProps="RuntimeIdentifier=${{ env.RuntimeIdentifier }}"
- -p:GeneratePackageOnBuild=false
+ -p:GeneratePackageOnBuild=false -p:CI=false
- name: Test natives outputs for ${{ matrix.rid }}
shell: pwsh
run: >
dotnet test Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj --no-restore --no-build -v ${{ env.DotnetVerbose }}
+ ${{ env.GenerateBinLog == 'true' && '-bl:test.binlog' || '' }}
+ --logger "console;verbosity=detailed"
--logger GitHubActions
- - name: List built for ${{ matrix.rid }}
- if: ${{ always() }}
- shell: pwsh
- run: |
- $rid = '${{ matrix.rid }}'
- $tripletBase = '${{ matrix.vcpkg-platform-target }}-${{ matrix.vcpkg-os-target }}'
- $suffix = if ($IsWindows) { 'static' } else { 'dynamic' }
- $triplets = @("$tripletBase", "$tripletBase-$suffix")
-
- foreach ($variant in @('natives','natives-static')) {
- foreach ($triplet in @($triplets)) {
- foreach ($folder in @('bin','lib')) {
- $path = "NetCord.Natives/bin/$rid/$variant/$triplet/$folder"
- if (Test-Path $path) {
- Get-ChildItem -Path $path -Recurse
- } else {
- Write-Host "[Info] Not found: $path"
- }
- }
- }
- }
-
- name: Pack NuGet Package for ${{ matrix.rid }}
+ if: ${{ always() }}
shell: pwsh
run: >
dotnet pack NetCord.Natives/NetCord.Natives.csproj --no-restore --no-build -v ${{ env.DotnetVerbose }}
+ ${{ env.GenerateBinLog == 'true' && '-bl:pack.binlog' || '' }}
-o NetCord.Natives/bin/${{ matrix.rid }}
-p:CI=false
+ - name: Upload Binary Logs
+ if: ${{ env.GenerateBinLog == 'true' && always() }}
+ uses: actions/upload-artifact@v4.6.0
+ with:
+ name: ${{ matrix.rid }}-binlogs
+ path: |
+ *.binlog
+
- name: Upload NuGet Package Artifact
+ if: ${{ always() }}
uses: actions/upload-artifact@v7.0.1
with:
- name: NetCord.Natives.${{ matrix.rid }}.nupkg
- path: NetCord.Natives/bin/${{ matrix.rid }}/*.nupkg
+ name: NetCord.Natives.${{ matrix.rid }}.packages
+ path: |
+ NetCord.Natives/bin/${{ matrix.rid }}/*.nupkg
+ NetCord.Natives/bin/${{ matrix.rid }}/*.snupkg
- name: Save vcpkg cache
if: ${{ always() }}
@@ -145,20 +146,3 @@ jobs:
.vcpkg-cache
.vcpkg-downloads
key: vcpkg-${{ runner.os }}-${{ matrix.rid }}-${{ hashFiles('NetCord.Natives/vcpkg.json', 'NetCord.Natives/natives-ports/**') }}
-
- publish:
- needs: [build-test-natives]
- if: startsWith(github.ref, 'refs/tags/')
- runs-on: ubuntu-latest
- steps:
- - name: Download All NuGet Package Artifacts
- uses: actions/download-artifact@v8.0.1
- with:
- path: artifacts/pkgs
- pattern: NetCord.Natives*.nupkg
-
- - name: Publish packages
- env:
- KEY: ${{ secrets.NUGET_API_KEY }}
- run: |
- dotnet nuget push artifacts/pkgs/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate
diff --git a/.github/workflows/build-publish-natives.yml b/.github/workflows/build-publish-natives.yml
new file mode 100644
index 00000000..8850fb49
--- /dev/null
+++ b/.github/workflows/build-publish-natives.yml
@@ -0,0 +1,35 @@
+name: Build & Publish NetCord.Natives
+
+on:
+ workflow_dispatch:
+ push:
+ paths:
+ - 'NetCord.Natives/**'
+ - '.github/workflows/build-natives.yml'
+ - '.github/workflows/publish-natives.yml'
+ tags:
+ - "[0-9]+.[0-9]+.[0-9]+"
+ - "[0-9]+.[0-9]+.[0-9]+-*"
+
+jobs:
+ build-natives:
+ uses: ./.github/workflows/build-natives.yml
+
+ publish:
+ needs: [build-natives]
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Download All NuGet Package Artifacts
+ uses: actions/download-artifact@v8.0.1
+ with:
+ path: artifacts/pkgs
+ pattern: NetCord.Natives.*.packages
+ merge-multiple: true
+
+ - name: Publish packages
+ env:
+ KEY: ${{ secrets.NUGET_API_KEY }}
+ run: |
+ dotnet nuget push artifacts/pkgs/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push artifacts/pkgs/*.snupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d6c0ce12..175eebaf 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -51,11 +51,11 @@ jobs:
- name: Pack Packages
shell: *dev-shell
run: |
- dotnet pack NetCord -c Release --no-build
- dotnet pack NetCord.Services -c Release --no-build
- dotnet pack Hosting/NetCord.Hosting -c Release --no-build
- dotnet pack Hosting/NetCord.Hosting.Services -c Release --no-build
- dotnet pack Hosting/NetCord.Hosting.AspNetCore -c Release --no-build
+ dotnet pack NetCord -c Release --no-build -o pkgs/
+ dotnet pack NetCord.Services -c Release --no-build -o pkgs/
+ dotnet pack Hosting/NetCord.Hosting -c Release --no-build -o pkgs/
+ dotnet pack Hosting/NetCord.Hosting.Services -c Release --no-build -o pkgs/
+ dotnet pack Hosting/NetCord.Hosting.AspNetCore -c Release --no-build -o pkgs/
- name: Setup docs environment
shell: &docs-shell 'nix develop .#docs -c bash -eo pipefail {0}'
@@ -75,11 +75,8 @@ jobs:
with:
name: NuGet Packages
path: |
- NetCord/bin/Release/*.nupkg
- NetCord.Services/bin/Release/*.nupkg
- Hosting/NetCord.Hosting/bin/Release/*.nupkg
- Hosting/NetCord.Hosting.Services/bin/Release/*.nupkg
- Hosting/NetCord.Hosting.AspNetCore/bin/Release/*.nupkg
+ pkgs/*.nupkg
+ pkgs/*.snupkg
- name: Upload Documentation Artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 1a960262..ea6b070a 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -23,6 +23,7 @@
+
diff --git a/Documentation/guides/basic-concepts/installing-native-dependencies.md b/Documentation/guides/basic-concepts/installing-native-dependencies.md
index 9c9fe2d4..061d36f0 100644
--- a/Documentation/guides/basic-concepts/installing-native-dependencies.md
+++ b/Documentation/guides/basic-concepts/installing-native-dependencies.md
@@ -1,91 +1,109 @@
# Installing Native Dependencies
-This is a hidden guide that is not visible in the guides index! If you are reading this, you are probably looking for information on how to install native dependencies for HTTP interactions or voice.
+NetCord relies on several native libraries for high-performance audio processing and encryption. NetCord provides prebuilt native binaries via NuGet packages, which is the recommended way to manage these dependencies.
-## HTTP Interactions
+## Native Dependencies Context
-For HTTP interactions, [Libsodium](https://doc.libsodium.org/installation) is required.
+- **Libdave**: Essential for voice connection interactions.
+- **Libsodium**: Used for encryption. While NetCord defaults to native AES-GCM (which does not require Libsodium), Libsodium is a highly recommended production dependency. It acts as a fallback for the XChaCha20-Poly1305 encryption mode, ensuring your bot remains compatible if Discord switches to this mode for your connection.
+- **Opus**: A versatile audio codec required for any classes in NetCord prefixed with `Opus` (e.g., audio encoding/decoding).
+- **Zstd**: Used for efficient payload compression.
-## Voice
+## NuGet Packages (Recommended)
-For voice:
-- [Libdave](https://github.com/discord/libdave) is required.
-- [Libsodium](https://doc.libsodium.org/installation) **is not generally** required, but it **is highly recommended for production bots**. It is caused by the fact that generally @NetCord.Gateway.Voice.Encryption.Aes256GcmRtpSizeEncryption, which does not require Libsodium, is supported by Discord and is used by default. However, there is a small chance that Discord will not support this encryption mode for your connection. In this case, @NetCord.Gateway.Voice.Encryption.XChaCha20Poly1305RtpSizeEncryption, which does require Libsodium, is used by default.
-- [Opus](https://opus-codec.org/downloads) is only required when you are using `Opus` prefixed classes.
+NetCord distributes these dependencies as per-RID (Runtime Identifier) packages. This model automates binary resolution, ensuring the correct libraries are provided for your target platform.
-## Installation
+### Supported Platforms
+Packages are available for:
-### [Dynamic Linking](#tab/dynamic)
+| Platform | RID | NativeAOT Support | Size

|
+|----------|-----|-------------------------|-----------|
+| Windows x64 | `win-x64` | β (Static CRT /MT) | -blue) |
+| Windows ARM64 | `win-arm64` | β (Static CRT /MT) | -blue) |
+| Linux x64 | `linux-x64` | β (Dynamic CRT) | -blue) |
+| Linux ARM64 | `linux-arm64` | β (Dynamic CRT) | -blue) |
+| β macOS x64 | `osx-x64` | β (Dynamic CRT) | -blue) |
+| β macOS ARM64 | `osx-arm64` | β (Dynamic CRT) | -blue) |
-For dynamic linking, you can install Libsodium for the most popular platforms by referencing the [official Libsodium NuGet package](https://www.nuget.org/packages/libsodium).
+β Currently, the macOS packages are built and published, but functions verification tests are skipped due to limitations with `dyld`.
+They are still expected to work correctly, but we recommend testing on macOS before production use.
-### Manual or System-wide Installation
+## Dynamic Linking
+For standard .NET applications, you can simply reference the relevant packages for your target platforms. The runtimes will be correctly copied and loaded across platforms.
-#### Windows
-
-For dynamic linking on Windows, you need to use the dynamic link libraries (`libdave`, `libsodium`, and/or `opus`). Here's how to set it up:
-- Download or build the dynamic link libraries (`libdave`, `libsodium`, and/or `opus`) compatible with your development environment.
-
-- Place these files in the runtime directory of your application. This is the folder where your application's executable is located.
-
-#### Linux and MacOS
-
-Dynamic linking on Linux and MacOS involves using shared libraries (`libdave`, `libsodium`, and/or `opus`). You can install them using your system's package manager if available or follow these steps to install them manually:
-- Download or build the shared libraries (`libdave`, `libsodium`, and/or `opus`) that are compatible with your development environment.
-
-- Place these files in the runtime directory of your application, which is the folder where your application's executable is located.
-
-### [Static Linking](#tab/static)
-
-> [!NOTE]
-> Static linking requires [Native AOT](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot) compilation.
-
-#### Windows
-
-When using static linking on Windows, you need to link to the static libraries (`libdave`, `libsodium`, and/or `opus`). Here are the steps to set up static linking in your application:
-- Download or build the static libraries (`libdave`, `libsodium`, and/or `opus`) compatible with your development environment.
-- Link these libraries in your project settings. Ensure that you specify the correct paths to these libraries:
- ```xml
-
-
-
-
-
-
-
-
-
-
- ```
+```xml
+
+
+
+
+
+```
-You don't need to place any of these files in the runtime directory, as static linking embeds the library code directly into your application, eliminating the need for separate files.
+## Usage with NativeAOT (Static Linking)
-#### Linux and MacOS
+When using [NativeAOT](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot), static libraries are automatically linked from the NuGet package, embedding the necessary native code directly into your executable.
-Static linking on Linux and MacOS involves linking your application with the static libraries (`libdave`, `libsodium`, and/or `opus`). You can install them using your system's package manager if available or download or build the static libraries (`libdave`, `libsodium`, and/or `opus`) compatible with your development environment manually.
+### Requirement for DirectPInvoke
+When targeting NativeAOT, you must ensure that all used native libraries are explicitly registered using `` in your project file to ensure they are properly included during the AOT compilation process:
-Link these libraries in your project settings. Make sure you specify the correct paths to these libraries:
```xml
-
-
-
-
-
+
```
-Since you're statically linking, you won't need to place any of these files in the runtime directory. The necessary code from the libraries will be included directly in your application.
+### Excluding Bundled Dependencies
+If you have provided your own native binaries and need to exclude the ones bundled in the NetCord NuGet package to avoid conflicts, use the `NetCordExcludeNatives` property.
+
+```xml
+
+ opus
+
+```
-***
+## Custom Manual/System-wide Installation
-#### Installation External Links
+If you are developing for an unsupported platform or require a custom-built native binary, you may choose to handle native dependencies manually.
+
+1. **Obtain Binaries**: Use your system's package manager (e.g., `apt`, `brew`, `dnf`) if available, or download the binaries from the official sources listed below.
+2. **Placement**: Ensure the native libraries are available to your application at runtime. On Windows, place `.dll` files in your application's output directory. On Unix-like systems, ensure shared libraries are in your `LD_LIBRARY_PATH` or system standard paths.
+
+### Installation External Links
| Library | Installation Link |
|-----------|---------------------------------------------|
| Libdave | https://github.com/discord/libdave/releases |
| Libsodium | https://doc.libsodium.org/installation |
| Opus | https://opus-codec.org/downloads |
+| Zstd | https://github.com/facebook/zstd/releases |
+
+## Troubleshooting
+- If you encounter issues with native dependencies, ensure that the correct versions are installed and that they are accessible to your application.
+- Use tools like `ldd` (Linux) or `Dependency Walker` (Windows) to verify that your application is correctly linking against the native libraries.
+
+---
+
+## Extra Notes on Native Dependencies
+
+The native packaging model is designed to make managing dependencies as transparent and reliable as possible.
+
+### What You Get
+* **Ready-to-use binaries**: We provide prebuilt runtime binaries for standard .NET and static libraries for NativeAOT.
+* **Automatic Configuration**: MSBuild files are included in the packages to automatically handle paths and linking requirements, so you don't have to manually configure build settings.
+
+### Built-in Compliance
+All necessary licenses and copyright notices from the original native project sources are bundled automatically within each package under the `licenses/` directory.
+
+| Library | License | Remarks |
+| :--- | :--- | :--- |
+| **Libdave** | MIT | Discord voice communication |
+| **Libsodium** | ISC | Used for encryption |
+| **OpenSSL** | Apache 2.0 | Cryptographic operations |
+| **Opus** | BSD-3-Clause | Audio codec |
+| **Zstd** | BSD-3-Clause | Compression |
+
+### How Itβs Built
+* **Reproducible builds**: Dependencies are strictly pinned using [vcpkg](https://github.com/microsoft/vcpkg) baselines. This means every build result should be identical, regardless of who or what runs the build.
diff --git a/NetCord.Natives/NativesHelper.cs b/NetCord.Natives/NativesHelper.cs
deleted file mode 100644
index 866a421b..00000000
--- a/NetCord.Natives/NativesHelper.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Reflection;
-
-namespace NetCord.Natives;
-
-[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
-public class NativeLibraryVersionAttribute(string name, string version) : Attribute
-{
- public string Name { get; } = name;
- public string Version { get; } = version;
-}
-
-public static class NativesHelper
-{
- public static IEnumerable GetNativeLibraryVersions()
- {
- return typeof(NativesHelper).Assembly.GetCustomAttributes();
- }
-}
diff --git a/NetCord.Natives/NetCord.Natives.csproj b/NetCord.Natives/NetCord.Natives.csproj
index 3d38cf8c..a13b761d 100644
--- a/NetCord.Natives/NetCord.Natives.csproj
+++ b/NetCord.Natives/NetCord.Natives.csproj
@@ -3,6 +3,9 @@
Pre-built native libraries for NetCord, $(Description)
false
+ false
+ false
+ true
$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))
@@ -123,7 +126,7 @@
Condition="'$(VcpkgUseStatic)' != 'true'" />
-