From 4b3d111d51e7b9fa453cace11e9c1ee9fd08dee3 Mon Sep 17 00:00:00 2001 From: Haves Irfan <20160532+ha-ves@users.noreply.github.com> Date: Mon, 27 Apr 2026 03:58:51 +0900 Subject: [PATCH 01/10] Pivot to use vcpkg to build natives * put into nuget package --- .gitmodules | 3 + NetCord.Natives/.gitignore | 1 + NetCord.Natives/NetCord.Natives.csproj | 80 +++++++++++++++++++ NetCord.Natives/NetCord.Natives.props | 7 ++ NetCord.Natives/NetCord.Natives.targets | 42 ++++++++++ .../natives-ports/libdave/portfile.cmake | 29 +++++++ .../natives-ports/libdave/vcpkg.json | 18 +++++ .../natives-ports/mlspp/portfile.cmake | 29 +++++++ .../natives-ports/mlspp/vcpkg.json | 17 ++++ NetCord.Natives/vcpkg | 1 + NetCord.Natives/vcpkg.json | 12 +++ NetCord.slnx | 1 + 12 files changed, 240 insertions(+) create mode 100644 .gitmodules create mode 100644 NetCord.Natives/.gitignore create mode 100644 NetCord.Natives/NetCord.Natives.csproj create mode 100644 NetCord.Natives/NetCord.Natives.props create mode 100644 NetCord.Natives/NetCord.Natives.targets create mode 100644 NetCord.Natives/natives-ports/libdave/portfile.cmake create mode 100644 NetCord.Natives/natives-ports/libdave/vcpkg.json create mode 100644 NetCord.Natives/natives-ports/mlspp/portfile.cmake create mode 100644 NetCord.Natives/natives-ports/mlspp/vcpkg.json create mode 160000 NetCord.Natives/vcpkg create mode 100644 NetCord.Natives/vcpkg.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..c33f058f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "NetCord.Natives/vcpkg"] + path = NetCord.Natives/vcpkg + url = https://github.com/microsoft/vcpkg.git diff --git a/NetCord.Natives/.gitignore b/NetCord.Natives/.gitignore new file mode 100644 index 00000000..07db887c --- /dev/null +++ b/NetCord.Natives/.gitignore @@ -0,0 +1 @@ +*.tlog diff --git a/NetCord.Natives/NetCord.Natives.csproj b/NetCord.Natives/NetCord.Natives.csproj new file mode 100644 index 00000000..37804cd0 --- /dev/null +++ b/NetCord.Natives/NetCord.Natives.csproj @@ -0,0 +1,80 @@ +ο»Ώ + + + Pre-built binaries for NetCord, $(Description) + false + + bin\natives + vcpkg/scripts/buildsystems/msbuild/vcpkg + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..')) + + + + + + + vcpkg\%(Filename)%(Extension) + + + + + + + + true + + windows + x64 + $(Platform) + + debug + $(VcpkgInstalledDir)\$(VcpkgPlatformTarget)-$(VcpkgOSTarget)\$(DebugDefinedDir) + + + + + Built Natives $(DebugDefinedDir)\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + + + + + + + + + + + + + + + + + + + + %(RecursiveDir)%(FileName)%(Extension) + true + runtimes\$(VcpkgOSTarget)-$(VcpkgPlatformTarget)\$(DebugDefinedDir)\ + + + + true + build\ + + + + + + + + + + diff --git a/NetCord.Natives/NetCord.Natives.props b/NetCord.Natives/NetCord.Natives.props new file mode 100644 index 00000000..ecee165d --- /dev/null +++ b/NetCord.Natives/NetCord.Natives.props @@ -0,0 +1,7 @@ + + + + $(MSBuildThisFileDirectory)..\runtimes\ + + + diff --git a/NetCord.Natives/NetCord.Natives.targets b/NetCord.Natives/NetCord.Natives.targets new file mode 100644 index 00000000..c65c04f8 --- /dev/null +++ b/NetCord.Natives/NetCord.Natives.targets @@ -0,0 +1,42 @@ + + + + + + true + true + true + + true + true + true + + true + + <_NetCordNativesPath>$(NetCordNativesPath)\$(RuntimeIdentifier) + + + + + + + + + + %(NativeFilter.Link) + %(Filename)%(Extension) + PreserveNewest + + + + + + + + + + + + diff --git a/NetCord.Natives/natives-ports/libdave/portfile.cmake b/NetCord.Natives/natives-ports/libdave/portfile.cmake new file mode 100644 index 00000000..8568d022 --- /dev/null +++ b/NetCord.Natives/natives-ports/libdave/portfile.cmake @@ -0,0 +1,29 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO discord/libdave + REF "${VERSION}" + SHA512 78b4e5b8ddc6397775d403465e0da770ec7905d7913546b3aec161baf4478443e554f0ae7bd012af8bfd308639be2601d46da22c02aff2b756ff91878f1fc843 + HEAD_REF main +) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}/cpp" + OPTIONS + -DTESTING=OFF + -DBUILD_TESTING=OFF + MAYBE_UNUSED_VARIABLES + BUILD_TESTING +) + +vcpkg_cmake_build() + +vcpkg_cmake_install() +vcpkg_copy_pdbs() + +# file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") + +# file(INSTALL +# "${SOURCE_PATH}/LICENSE" +# DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" +# RENAME LICENSE.txt +# ) \ No newline at end of file diff --git a/NetCord.Natives/natives-ports/libdave/vcpkg.json b/NetCord.Natives/natives-ports/libdave/vcpkg.json new file mode 100644 index 00000000..9f74f4d3 --- /dev/null +++ b/NetCord.Natives/natives-ports/libdave/vcpkg.json @@ -0,0 +1,18 @@ +{ + "name": "libdave", + "version-string": "52cd56dc550f447fb354b3a06c9e2d2e2a4309c6", + "license": "MIT", + "dependencies": [ + "openssl", + "mlspp", + "nlohmann-json", + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ] +} \ No newline at end of file diff --git a/NetCord.Natives/natives-ports/mlspp/portfile.cmake b/NetCord.Natives/natives-ports/mlspp/portfile.cmake new file mode 100644 index 00000000..66dd943c --- /dev/null +++ b/NetCord.Natives/natives-ports/mlspp/portfile.cmake @@ -0,0 +1,29 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO cisco/mlspp + REF "${VERSION}" + SHA512 5d37631e2c47daae1133ef074e60cc09ca2d395f9e11c416f829060e374051cf219d2d7fe98dae49d1d045292e07d6a09f4814a5f16e6cc05e67e7cd96f146c4 +) + +if(VCPKG_TARGET_IS_OSX AND EXISTS "/usr/local/include/openssl/") + set(VCPKG_INCLUDE_OVERRIDE "-DCMAKE_CXX_FLAGS=-I${CURRENT_INSTALLED_DIR}/include") +endif() + +set(VCPKG_LIBRARY_LINKAGE static) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + ${VCPKG_INCLUDE_OVERRIDE} + -DDISABLE_GREASE=ON + -DTESTING=OFF + -DBUILD_TESTING=OFF + -DMLS_CXX_NAMESPACE="mlspp" + MAYBE_UNUSED_VARIABLES + BUILD_TESTING +) + +vcpkg_cmake_install() +vcpkg_copy_pdbs() + +vcpkg_cmake_config_fixup(PACKAGE_NAME "MLSPP" CONFIG_PATH "share/MLSPP") diff --git a/NetCord.Natives/natives-ports/mlspp/vcpkg.json b/NetCord.Natives/natives-ports/mlspp/vcpkg.json new file mode 100644 index 00000000..fca86978 --- /dev/null +++ b/NetCord.Natives/natives-ports/mlspp/vcpkg.json @@ -0,0 +1,17 @@ +{ + "name": "mlspp", + "version-string": "1cc50a124a3bc4e143a787ec934280dc70c1034d", + "description": "Cisco MLS C++ library", + "dependencies": [ + "openssl", + "nlohmann-json", + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ] +} diff --git a/NetCord.Natives/vcpkg b/NetCord.Natives/vcpkg new file mode 160000 index 00000000..89dd0f4d --- /dev/null +++ b/NetCord.Natives/vcpkg @@ -0,0 +1 @@ +Subproject commit 89dd0f4d241136b843fb55813b2f0fa6448c204d diff --git a/NetCord.Natives/vcpkg.json b/NetCord.Natives/vcpkg.json new file mode 100644 index 00000000..426671ba --- /dev/null +++ b/NetCord.Natives/vcpkg.json @@ -0,0 +1,12 @@ +{ + "dependencies": [ + "libdave", + "opus", + "libsodium" + ], + "vcpkg-configuration": { + "overlay-ports": [ + "natives-ports" + ] + } +} diff --git a/NetCord.slnx b/NetCord.slnx index 23417afd..fd20607e 100644 --- a/NetCord.slnx +++ b/NetCord.slnx @@ -97,6 +97,7 @@ + From 48bfd5b36a23e97fd80f36982c2aa23451ae85e6 Mon Sep 17 00:00:00 2001 From: Haves Irfan <20160532+ha-ves@users.noreply.github.com> Date: Thu, 30 Apr 2026 00:38:00 +0900 Subject: [PATCH 02/10] * Add NetCord.Natives project and vcpkg support * Add native ports for libdave and mlspp * Update build workflows for native packaging * Add local targets and CI helpers for natives * Update README --- .github/workflows/build-and-publish.yml | 85 +++------- .github/workflows/build-natives.yml | 143 ++++++++++++++++ .github/workflows/build.yml | 1 + NetCord.Natives/NetCord.Natives.csproj | 159 ++++++++++++++---- NetCord.Natives/NetCord.Natives.local.targets | 17 ++ NetCord.Natives/NetCord.Natives.props | 10 +- NetCord.Natives/NetCord.Natives.targets | 44 ++--- .../natives-ports/libdave/portfile.cmake | 12 +- README.md | 3 + Resources/NuGet/README.md | 3 + 10 files changed, 337 insertions(+), 140 deletions(-) create mode 100644 .github/workflows/build-natives.yml create mode 100644 NetCord.Natives/NetCord.Natives.local.targets diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 96ee3621..a27c0d24 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -7,15 +7,25 @@ on: - "[0-9]+.[0-9]+.[0-9]+-*" jobs: - build-and-publish: + build: + uses: ./.github/workflows/build.yml + + publish: + needs: build runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Download Build Artifacts + uses: actions/download-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + name: Build Artifacts + path: artifacts/build + + - name: Download Documentation Artifacts + uses: actions/download-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: - fetch-depth: 0 - filter: tree:0 + name: Documentation Artifacts + path: artifacts/docs - name: Install Nix uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6 @@ -24,76 +34,25 @@ jobs: shell: &dev-shell 'nix develop -c bash -eo pipefail {0}' run: 'true' - - name: Restore dependencies - shell: *dev-shell - run: dotnet restore - - - name: Build - shell: *dev-shell - run: dotnet build -c Release --no-restore --warnaserror - - - name: Test - shell: *dev-shell - run: dotnet test -c Release --no-build --verbosity normal - - - 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 - - - name: Setup docs environment - shell: &docs-shell 'nix develop .#docs -c bash -eo pipefail {0}' - run: 'true' - - - name: Build Documentation - shell: *docs-shell - working-directory: Documentation - run: | - npm install - npm run lint - npm run test - npm run build - - name: Publish Packages shell: *dev-shell env: KEY: ${{ secrets.NUGET_API_KEY }} run: | - dotnet nuget push NetCord/bin/Release/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate - dotnet nuget push NetCord.Services/bin/Release/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate - dotnet nuget push Hosting/NetCord.Hosting/bin/Release/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate - dotnet nuget push Hosting/NetCord.Hosting.Services/bin/Release/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate - dotnet nuget push Hosting/NetCord.Hosting.AspNetCore/bin/Release/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate + dotnet nuget push artifacts/build/NetCord/bin/Release/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate + dotnet nuget push artifacts/build/NetCord.Services/bin/Release/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate + dotnet nuget push artifacts/build/Hosting/NetCord.Hosting/bin/Release/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate + dotnet nuget push artifacts/build/Hosting/NetCord.Hosting.Services/bin/Release/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate + dotnet nuget push artifacts/build/Hosting/NetCord.Hosting.AspNetCore/bin/Release/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate - name: Deploy Documentation - uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 + uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 with: username: ${{ secrets.SSH_USERNAME }} host: ${{ secrets.SSH_HOST }} port: ${{ secrets.SSH_PORT }} key: ${{ secrets.SSH_KEY }} rm: true - source: Documentation/_site + source: artifacts/docs strip_components: 2 target: ~/NetCord/html - - - name: Upload Build Artifacts - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: Build Artifacts - path: | - NetCord/bin/Release - NetCord.Services/bin/Release - Hosting/NetCord.Hosting/bin/Release - Hosting/NetCord.Hosting.Services/bin/Release - Hosting/NetCord.Hosting.AspNetCore/bin/Release - - - name: Upload Documentation Artifacts - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: Documentation Artifacts - path: Documentation/_site diff --git a/.github/workflows/build-natives.yml b/.github/workflows/build-natives.yml new file mode 100644 index 00000000..ed3496f2 --- /dev/null +++ b/.github/workflows/build-natives.yml @@ -0,0 +1,143 @@ +name: Build NetCord.Natives + +on: + workflow_dispatch: {} + push: + paths: + - 'NetCord.Natives/**' + +jobs: + build-natives: + runs-on: ${{ matrix.runs-on }} + env: + CI: true + VCPKG_BINARY_SOURCES: clear;files,${{ github.workspace }}/.vcpkg-cache,readwrite + VCPKG_DOWNLOADS: ${{ github.workspace }}/.vcpkg-downloads + strategy: + fail-fast: false + matrix: + include: + - rid: win-x64 + runs-on: windows-latest + vcpkg-os-target: windows + vcpkg-platform-target: x64 + - rid: win-arm64 + runs-on: windows-latest + vcpkg-os-target: windows + vcpkg-platform-target: arm64 + - rid: osx-x64 + runs-on: macos-latest + vcpkg-os-target: osx + vcpkg-platform-target: x64 + - rid: osx-arm64 + runs-on: macos-latest + vcpkg-os-target: osx + vcpkg-platform-target: arm64 + - rid: linux-x64 + runs-on: ubuntu-latest + vcpkg-os-target: linux + vcpkg-platform-target: x64 + - rid: linux-arm64 + runs-on: ubuntu-latest + vcpkg-os-target: linux + vcpkg-platform-target: arm64 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Cache vcpkg installs + uses: actions/cache@v4 + with: + path: | + .vcpkg-cache + .vcpkg-downloads + key: vcpkg-${{ runner.os }}-${{ matrix.rid }}-${{ hashFiles('NetCord.Natives/vcpkg.json') }} + restore-keys: | + vcpkg-${{ runner.os }}-${{ matrix.rid }}- + vcpkg-${{ runner.os }}- + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Resolve vcpkg from runner image + shell: pwsh + run: | + $vcpkgRoot = $env:VCPKG_INSTALLATION_ROOT + if (-not $vcpkgRoot) { + $vcpkgCommand = Get-Command vcpkg -ErrorAction Stop + $vcpkgRoot = Split-Path -Parent $vcpkgCommand.Source + } + + if (-not (Test-Path $vcpkgRoot)) { + throw "vcpkg installation root not found." + } + + "VCPKG_ROOT=$vcpkgRoot" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + "VcpkgMsbuildPath=$vcpkgRoot/scripts/buildsystems/msbuild/vcpkg" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + + - name: Restore + shell: pwsh + run: | + dotnet restore NetCord.Natives/NetCord.Natives.csproj -p:VcpkgMsbuildPath=$env:VcpkgMsbuildPath -p:VcpkgOSTarget=${{ matrix.vcpkg-os-target }} -p:VcpkgPlatformTarget=${{ matrix.vcpkg-platform-target }} + + - name: Build native outputs for ${{ matrix.rid }} + shell: pwsh + run: | + dotnet build NetCord.Natives/NetCord.Natives.csproj -c Release --no-restore -p:GeneratePackageOnBuild=false -p:VcpkgMsbuildPath=$env:VcpkgMsbuildPath -p:VcpkgOSTarget=${{ matrix.vcpkg-os-target }} -p:VcpkgPlatformTarget=${{ matrix.vcpkg-platform-target }} + + - name: List artifacts + shell: pwsh + run: | + Get-ChildItem -Recurse NetCord.Natives/bin | Select-Object FullName + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.rid }} + path: NetCord.Natives/bin + + pack-natives: + needs: build-natives + runs-on: ubuntu-latest + env: + CI: true + NativeArtifactsRoot: ${{ github.workspace }}/artifacts + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download native artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: false + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Resolve native license root + shell: pwsh + run: | + $licenseRoot = Get-ChildItem artifacts -Directory | Sort-Object Name | Select-Object -First 1 + if (-not $licenseRoot) { + throw "No native artifact directories were downloaded." + } + + "NativeLicenseRoot=$($licenseRoot.FullName)" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + + - name: Pack combined nupkg + shell: pwsh + run: | + dotnet pack NetCord.Natives/NetCord.Natives.csproj -c Release --no-build --no-restore -p:VcpkgMsbuildPath=$env:VcpkgMsbuildPath -p:NativeArtifactsRoot=$env:NativeArtifactsRoot -p:NativeLicenseRoot=$env:NativeLicenseRoot -o artifacts/package + + - name: Upload nupkg + uses: actions/upload-artifact@v4 + with: + name: NetCord.Natives.Package + path: artifacts/package/*.nupkg diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 350387e0..40c35ec0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,7 @@ name: Build on: + workflow_call: pull_request: branches: - stable diff --git a/NetCord.Natives/NetCord.Natives.csproj b/NetCord.Natives/NetCord.Natives.csproj index 37804cd0..ef8e1acb 100644 --- a/NetCord.Natives/NetCord.Natives.csproj +++ b/NetCord.Natives/NetCord.Natives.csproj @@ -1,45 +1,63 @@ ο»Ώ - Pre-built binaries for NetCord, $(Description) + Pre-built native libraries for NetCord, $(Description) false - bin\natives - vcpkg/scripts/buildsystems/msbuild/vcpkg $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..')) - - + + vcpkg\%(Filename)%(Extension) - - + debug + $(MSBuildProjectDirectory)\artifacts + + vcpkg/scripts/buildsystems/msbuild/vcpkg true - windows + windows + osx + linux + x64 $(Platform) - debug - $(VcpkgInstalledDir)\$(VcpkgPlatformTarget)-$(VcpkgOSTarget)\$(DebugDefinedDir) + bin\natives + $(VcpkgInstalledDir)-static + + $(VcpkgInstalledDir)\$(VcpkgPlatformTarget)-$(VcpkgOSTarget) + $(VcpkgInstalledDir)-static\$(VcpkgPlatformTarget)-$(VcpkgOSTarget)-static + $(BuildNativeOutputPath) + + ClCompile;_CopyNativeLicenses - - Built Natives $(DebugDefinedDir)\%(RecursiveDir)%(Filename)%(Extension) - + + + + + + + <_NetCordStaticFullPath Include="$([System.IO.Path]::GetFullPath('$(BuildNativeStaticOutputPath)'))" /> + + + + + - + @@ -47,34 +65,111 @@ - + + + + + + + + + + + + <_NativeLicenseSource Include="$(BuildNativeOutputPath)\share\%(AllowedNativesLicenses.Identity)\copyright" Condition="Exists('$(BuildNativeOutputPath)\share\%(AllowedNativesLicenses.Identity)\copyright')"> + $(BuildNativeOutputPath)\licenses\%(AllowedNativesLicenses.Identity)_LICENSE.txt + + + + - + + + + + + %(Filename)%(Extension) + PreserveNewest + + + + + + + $(PackageId).$(VcpkgOSTarget)-$(VcpkgPlatformTarget) + + + + runtimes\$(VcpkgOSTarget)-$(VcpkgPlatformTarget)\native\%(Filename)%(Extension) + + + runtimes-static\$(VcpkgOSTarget)-$(VcpkgPlatformTarget)\%(Filename)%(Extension) + + + + + <_NativeRuntime Include="win-x64"> + runtimes\win-x64\native\ + runtimes-static\win-x64\ + + <_NativeRuntime Include="win-arm64"> + runtimes\win-arm64\native\ + runtimes-static\win-arm64\ + + <_NativeRuntime Include="osx-x64"> + runtimes\osx-x64\native\ + runtimes-static\osx-x64\ + + <_NativeRuntime Include="osx-arm64"> + runtimes\osx-arm64\native\ + runtimes-static\osx-arm64\ + + <_NativeRuntime Include="linux-x64"> + runtimes\linux-x64\native\ + runtimes-static\linux-x64\ + + <_NativeRuntime Include="linux-arm64"> + runtimes\linux-arm64\native\ + runtimes-static\linux-arm64\ + + + + %(_NativeRuntime.PackageRuntimePath)%(Filename)%(Extension) + + + %(_NativeRuntime.PackageStaticPath)%(Filename)%(Extension) + + - - - - + + true + %(NativesBuilt.TargetPath) + - - %(RecursiveDir)%(FileName)%(Extension) + true - runtimes\$(VcpkgOSTarget)-$(VcpkgPlatformTarget)\$(DebugDefinedDir)\ + licenses\ - - + + <_BuildFiles Include="$(MSBuildProjectName).props;$(MSBuildProjectName).targets"> true - build\ + + + + build\$(PackageId)%(Extension) + + + buildTransitive\$(PackageId)%(Extension) - - - - - + + + + + diff --git a/NetCord.Natives/NetCord.Natives.local.targets b/NetCord.Natives/NetCord.Natives.local.targets new file mode 100644 index 00000000..1c79590d --- /dev/null +++ b/NetCord.Natives/NetCord.Natives.local.targets @@ -0,0 +1,17 @@ + + + + + + + + + + + + @(_NetCordStaticDir->'%(Identity)')\lib + + + + + diff --git a/NetCord.Natives/NetCord.Natives.props b/NetCord.Natives/NetCord.Natives.props index ecee165d..021708eb 100644 --- a/NetCord.Natives/NetCord.Natives.props +++ b/NetCord.Natives/NetCord.Natives.props @@ -1,7 +1,7 @@ - - - $(MSBuildThisFileDirectory)..\runtimes\ - - + + + $(MSBuildThisFileDirectory)..\runtimes-static\$(RuntimeIdentifier) + + diff --git a/NetCord.Natives/NetCord.Natives.targets b/NetCord.Natives/NetCord.Natives.targets index c65c04f8..16d61ae6 100644 --- a/NetCord.Natives/NetCord.Natives.targets +++ b/NetCord.Natives/NetCord.Natives.targets @@ -1,42 +1,20 @@ - + - - true - true - true + + <_NetCordStaticLink Include="libdave;mlspp;bytes;tls_syntax;hpke;libcrypto" + Condition="'%(DirectPInvoke.Identity)' == 'libdave'" /> + <_NetCordStaticLink Include="libsodium" + Condition="'%(DirectPInvoke.Identity)' == 'libsodium'" /> + <_NetCordStaticLink Include="opus" + Condition="'%(DirectPInvoke.Identity)' == 'opus'" /> - true - true - true + - true - - <_NetCordNativesPath>$(NetCordNativesPath)\$(RuntimeIdentifier) - - - - - - - - - - %(NativeFilter.Link) - %(Filename)%(Extension) - PreserveNewest - - - - - - - - + + diff --git a/NetCord.Natives/natives-ports/libdave/portfile.cmake b/NetCord.Natives/natives-ports/libdave/portfile.cmake index 8568d022..a001f80e 100644 --- a/NetCord.Natives/natives-ports/libdave/portfile.cmake +++ b/NetCord.Natives/natives-ports/libdave/portfile.cmake @@ -20,10 +20,8 @@ vcpkg_cmake_build() vcpkg_cmake_install() vcpkg_copy_pdbs() -# file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") - -# file(INSTALL -# "${SOURCE_PATH}/LICENSE" -# DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" -# RENAME LICENSE.txt -# ) \ No newline at end of file +file(INSTALL + "${SOURCE_PATH}/LICENSE" + DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" + RENAME copyright +) diff --git a/README.md b/README.md index b3d29496..cdbb638f 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ You can install NetCord packages via NuGet package manager: | **[NetCord.Hosting](https://www.nuget.org/packages/NetCord.Hosting)** | Provides .NET Generic Host extensions for the NetCord package. | | **[NetCord.Hosting.Services](https://www.nuget.org/packages/NetCord.Hosting.Services)** | Provides .NET Generic Host extensions for the NetCord.Services package. | | **[NetCord.Hosting.AspNetCore](https://www.nuget.org/packages/NetCord.Hosting.AspNetCore)** | Provides ASP.NET Core extensions for seamless handling of HTTP events. | +| **[NetCord.Natives](https://www.nuget.org/packages/NetCord.Natives)** | Provides pre-built native binaries dependencies. | ## 2. πŸš€ Showcase @@ -116,6 +117,8 @@ NetCord's goal is to allow .NET developers to create fully customizable Discord This repository is released under the [MIT License](LICENSE.md). +The use of NetCord.Natives may subject to each libraries licenses. + ## 9. πŸ› οΈ Development ### Versioning diff --git a/Resources/NuGet/README.md b/Resources/NuGet/README.md index 89a1d414..d7babe22 100644 --- a/Resources/NuGet/README.md +++ b/Resources/NuGet/README.md @@ -25,6 +25,7 @@ You can install NetCord packages via NuGet package manager: | **[NetCord.Hosting](https://www.nuget.org/packages/NetCord.Hosting)** | Provides .NET Generic Host extensions for the NetCord package. | | **[NetCord.Hosting.Services](https://www.nuget.org/packages/NetCord.Hosting.Services)** | Provides .NET Generic Host extensions for the NetCord.Services package. | | **[NetCord.Hosting.AspNetCore](https://www.nuget.org/packages/NetCord.Hosting.AspNetCore)** | Provides ASP.NET Core extensions for seamless handling of HTTP events. | +| **[NetCord.Natives](https://www.nuget.org/packages/NetCord.Natives)** | Provides pre-built native binaries dependencies. | ## 2. πŸš€ Showcase @@ -95,6 +96,8 @@ NetCord's goal is to allow .NET developers to create fully customizable Discord This repository is released under the [MIT License](https://github.com/NetCordDev/NetCord/blob/alpha/LICENSE.md). +The use of NetCord.Natives may subject to each libraries licenses. + ## 9. πŸ› οΈ Development ### Versioning From f0030a572bb98d807ee05408a9c90ce736b90130 Mon Sep 17 00:00:00 2001 From: Haves Irfan <20160532+ha-ves@users.noreply.github.com> Date: Fri, 1 May 2026 05:56:49 +0900 Subject: [PATCH 03/10] feat: native library support and testing - GitHub Actions tag triggers, NuGet publish job - NativesHelper, NativeLibraryVersionAttribute - vcpkg integration, static linking - zstd support - native library tests, import checks - Native AOT test app - NetCordNativesDir assembly metadata --- .github/workflows/build-natives.yml | 41 +- NetCord.Natives/NativesHelper.cs | 20 + NetCord.Natives/NetCord.Natives.csproj | 389 ++++++++++-------- NetCord.Natives/NetCord.Natives.local.targets | 3 +- NetCord.Natives/NetCord.Natives.props | 2 +- NetCord.Natives/NetCord.Natives.targets | 2 + NetCord.Natives/vcpkg.json | 3 +- NetCord.slnx | 1 + Tests/NetCord.Natives.Tests/AssemblyInfo.cs | 3 + .../Assets/NativeAotApp/NativeAotApp.csproj | 24 ++ .../Assets/NativeAotApp/Program.cs | 90 ++++ .../NativesBuildTests.cs | 134 ++++++ .../NetCord.Natives.Tests.csproj | 44 ++ 13 files changed, 562 insertions(+), 194 deletions(-) create mode 100644 NetCord.Natives/NativesHelper.cs create mode 100644 Tests/NetCord.Natives.Tests/AssemblyInfo.cs create mode 100644 Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj create mode 100644 Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs create mode 100644 Tests/NetCord.Natives.Tests/NativesBuildTests.cs create mode 100644 Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj diff --git a/.github/workflows/build-natives.yml b/.github/workflows/build-natives.yml index ed3496f2..91d27e9f 100644 --- a/.github/workflows/build-natives.yml +++ b/.github/workflows/build-natives.yml @@ -5,6 +5,10 @@ on: push: paths: - 'NetCord.Natives/**' + - '.github/workflows/build-natives.yml' + tags: + - "[0-9]+.[0-9]+.[0-9]+" + - "[0-9]+.[0-9]+.[0-9]+-*" jobs: build-natives: @@ -57,11 +61,6 @@ jobs: vcpkg-${{ runner.os }}-${{ matrix.rid }}- vcpkg-${{ runner.os }}- - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x - - name: Resolve vcpkg from runner image shell: pwsh run: | @@ -116,21 +115,6 @@ jobs: path: artifacts merge-multiple: false - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x - - - name: Resolve native license root - shell: pwsh - run: | - $licenseRoot = Get-ChildItem artifacts -Directory | Sort-Object Name | Select-Object -First 1 - if (-not $licenseRoot) { - throw "No native artifact directories were downloaded." - } - - "NativeLicenseRoot=$($licenseRoot.FullName)" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 - - name: Pack combined nupkg shell: pwsh run: | @@ -141,3 +125,20 @@ jobs: with: name: NetCord.Natives.Package path: artifacts/package/*.nupkg + + publish: + needs: pack-natives + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Download nupkg + uses: actions/download-artifact@v4 + with: + name: NetCord.Natives.Package + path: artifacts/package + + - name: Publish Package + env: + KEY: ${{ secrets.NUGET_API_KEY }} + run: | + dotnet nuget push artifacts/package/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate diff --git a/NetCord.Natives/NativesHelper.cs b/NetCord.Natives/NativesHelper.cs new file mode 100644 index 00000000..866a421b --- /dev/null +++ b/NetCord.Natives/NativesHelper.cs @@ -0,0 +1,20 @@ +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 ef8e1acb..0ea591d8 100644 --- a/NetCord.Natives/NetCord.Natives.csproj +++ b/NetCord.Natives/NetCord.Natives.csproj @@ -1,175 +1,222 @@ ο»Ώ - - Pre-built native libraries for NetCord, $(Description) - false - - $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..')) - - - - - - - vcpkg\%(Filename)%(Extension) - - - - - - debug - $(MSBuildProjectDirectory)\artifacts - - vcpkg/scripts/buildsystems/msbuild/vcpkg - true - - windows - osx - linux - - x64 - $(Platform) - - bin\natives - $(VcpkgInstalledDir)-static - - $(VcpkgInstalledDir)\$(VcpkgPlatformTarget)-$(VcpkgOSTarget) - $(VcpkgInstalledDir)-static\$(VcpkgPlatformTarget)-$(VcpkgOSTarget)-static - $(BuildNativeOutputPath) - - ClCompile;_CopyNativeLicenses - - - - - - - - - - - - <_NetCordStaticFullPath Include="$([System.IO.Path]::GetFullPath('$(BuildNativeStaticOutputPath)'))" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - <_NativeLicenseSource Include="$(BuildNativeOutputPath)\share\%(AllowedNativesLicenses.Identity)\copyright" Condition="Exists('$(BuildNativeOutputPath)\share\%(AllowedNativesLicenses.Identity)\copyright')"> - $(BuildNativeOutputPath)\licenses\%(AllowedNativesLicenses.Identity)_LICENSE.txt - - - - - - - - - - - - %(Filename)%(Extension) - PreserveNewest - - - - - - - $(PackageId).$(VcpkgOSTarget)-$(VcpkgPlatformTarget) - - - - runtimes\$(VcpkgOSTarget)-$(VcpkgPlatformTarget)\native\%(Filename)%(Extension) - - - runtimes-static\$(VcpkgOSTarget)-$(VcpkgPlatformTarget)\%(Filename)%(Extension) - - - - - <_NativeRuntime Include="win-x64"> - runtimes\win-x64\native\ - runtimes-static\win-x64\ - - <_NativeRuntime Include="win-arm64"> - runtimes\win-arm64\native\ - runtimes-static\win-arm64\ - - <_NativeRuntime Include="osx-x64"> - runtimes\osx-x64\native\ - runtimes-static\osx-x64\ - - <_NativeRuntime Include="osx-arm64"> - runtimes\osx-arm64\native\ - runtimes-static\osx-arm64\ - - <_NativeRuntime Include="linux-x64"> - runtimes\linux-x64\native\ - runtimes-static\linux-x64\ - - <_NativeRuntime Include="linux-arm64"> - runtimes\linux-arm64\native\ - runtimes-static\linux-arm64\ - - - - %(_NativeRuntime.PackageRuntimePath)%(Filename)%(Extension) - - - %(_NativeRuntime.PackageStaticPath)%(Filename)%(Extension) - - - - - - true - %(NativesBuilt.TargetPath) - - - - true - licenses\ - - - <_BuildFiles Include="$(MSBuildProjectName).props;$(MSBuildProjectName).targets"> - true - - - - build\$(PackageId)%(Extension) - - - buildTransitive\$(PackageId)%(Extension) - - - - - - - - + + Pre-built native libraries for NetCord, $(Description) + false + + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..')) + + + + + + + vcpkg\%(Filename)%(Extension) + + + + + + debug + + vcpkg/scripts/buildsystems/msbuild/vcpkg + true + + windows + osx + linux + + $(VcpkgOSTarget) + win + + x64 + $(Platform) + + bin + + $(NativeArtifactsRoot)\$(VcpkgBuildOS)-$(VcpkgPlatformTarget)\natives + $(VcpkgInstalledDir)-static + + $(VcpkgInstalledDir)\$(VcpkgPlatformTarget)-$(VcpkgOSTarget) + $(VcpkgInstalledDir)\$(VcpkgPlatformTarget)-$(VcpkgOSTarget)-static + + <_BuildNativeStaticOutputPath>$(VcpkgInstalledDir)\$(VcpkgPlatformTarget)-$(VcpkgOSTarget)-static + <_VcpkgStatusFile>$(VcpkgInstalledDir)\vcpkg\status + + + + + + + + + + + + + + <_NetCordStaticFullPath Include="$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\$(_BuildNativeStaticOutputPath)'))" /> + + + + + + + + + + + + + + + + + + + + + + + + <_NativeLicenseSource Include="$(BuildNativeOutputPath)\share\%(AllowedNativesLicenses.Identity)\copyright" + Condition="Exists('$(BuildNativeOutputPath)\share\%(AllowedNativesLicenses.Identity)\copyright')"> + $(BuildNativeOutputPath)\licenses\%(AllowedNativesLicenses.Identity)_LICENSE.txt + + + + + + + + + + + + + + + + @(StatusLines, ' ') + + + + + + + $([System.Text.RegularExpressions.Regex]::Match('$(StatusText)', 'Package: %(Identity)\n(?:.*\n)*?Version: (.*?)\n').Groups[1].Value) + + + + + + + <_Parameter1>%(PackageWithVersion.Identity) + <_Parameter2>%(PackageWithVersion.Version) + + + + + + + + + + + + + %(Filename)%(Extension) + PreserveNewest + false + + + + + + + + $(PackageId).$(VcpkgBuildOS)-$(VcpkgPlatformTarget) + + + + + runtimes\$(VcpkgBuildOS)-$(VcpkgPlatformTarget)\native\ + + + staticlibs\$(VcpkgBuildOS)-$(VcpkgPlatformTarget)\ + + + + + <_BuiltTriplets Include="x64-windows"> + win-x64 + x64-windows\bin\%(AllowedNativeLibrary.Identity) + x64-windows-static\lib\%(AllowedNativeLibrary.Identity) + + <_BuiltTriplets Include="arm64-windows"> + win-arm64 + arm64-windows\bin\%(AllowedNativeLibrary.Identity) + arm64-windows-static\lib\%(AllowedNativeLibrary.Identity) + + <_BuiltTriplets Include="x64-linux"> + linux-x64 + x64-linux\bin\%(AllowedNativeLibrary.Identity) + x64-linux-static\lib\%(AllowedNativeLibrary.Identity) + + <_BuiltTriplets Include="arm64-linux"> + linux-arm64 + arm64-linux\bin\%(AllowedNativeLibrary.Identity) + arm64-linux-static\lib\%(AllowedNativeLibrary.Identity) + + <_BuiltTriplets Include="x64-osx"> + osx-x64 + x64-osx\bin\%(AllowedNativeLibrary.Identity) + x64-osx-static\lib\%(AllowedNativeLibrary.Identity) + + <_BuiltTriplets Include="arm64-osx"> + osx-arm64 + arm64-osx\bin\%(AllowedNativeLibrary.Identity) + arm64-osx-static\lib\%(AllowedNativeLibrary.Identity) + + + + runtimes\%(_BuiltTriplets.RuntimeId)\native\ + + + staticlibs\%(_BuiltTriplets.RuntimeId)\ + + + + + + true + + + + true + licenses\ + + + <_BuildFiles Include="$(MSBuildProjectName).props;$(MSBuildProjectName).targets"> + true + + + + build\$(PackageId)%(Extension) + + + buildTransitive\$(PackageId)%(Extension) + + + + + + + + diff --git a/NetCord.Natives/NetCord.Natives.local.targets b/NetCord.Natives/NetCord.Natives.local.targets index 1c79590d..7a542e0e 100644 --- a/NetCord.Natives/NetCord.Natives.local.targets +++ b/NetCord.Natives/NetCord.Natives.local.targets @@ -4,7 +4,8 @@ - + diff --git a/NetCord.Natives/NetCord.Natives.props b/NetCord.Natives/NetCord.Natives.props index 021708eb..10bbd708 100644 --- a/NetCord.Natives/NetCord.Natives.props +++ b/NetCord.Natives/NetCord.Natives.props @@ -1,7 +1,7 @@ - $(MSBuildThisFileDirectory)..\runtimes-static\$(RuntimeIdentifier) + $(MSBuildThisFileDirectory)..\staticlibs\$(RuntimeIdentifier) diff --git a/NetCord.Natives/NetCord.Natives.targets b/NetCord.Natives/NetCord.Natives.targets index 16d61ae6..a9ab04c5 100644 --- a/NetCord.Natives/NetCord.Natives.targets +++ b/NetCord.Natives/NetCord.Natives.targets @@ -9,6 +9,8 @@ Condition="'%(DirectPInvoke.Identity)' == 'libsodium'" /> <_NetCordStaticLink Include="opus" Condition="'%(DirectPInvoke.Identity)' == 'opus'" /> + <_NetCordStaticLink Include="zstd" + Condition="'%(DirectPInvoke.Identity)' == 'zstd'" /> diff --git a/NetCord.Natives/vcpkg.json b/NetCord.Natives/vcpkg.json index 426671ba..1712ed4f 100644 --- a/NetCord.Natives/vcpkg.json +++ b/NetCord.Natives/vcpkg.json @@ -2,7 +2,8 @@ "dependencies": [ "libdave", "opus", - "libsodium" + "libsodium", + "zstd" ], "vcpkg-configuration": { "overlay-ports": [ diff --git a/NetCord.slnx b/NetCord.slnx index fd20607e..08ac717d 100644 --- a/NetCord.slnx +++ b/NetCord.slnx @@ -84,6 +84,7 @@ + diff --git a/Tests/NetCord.Natives.Tests/AssemblyInfo.cs b/Tests/NetCord.Natives.Tests/AssemblyInfo.cs new file mode 100644 index 00000000..ae411c7a --- /dev/null +++ b/Tests/NetCord.Natives.Tests/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj new file mode 100644 index 00000000..05c22e53 --- /dev/null +++ b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj @@ -0,0 +1,24 @@ + + + + Exe + net10.0 + enable + enable + + true + true + + + + + + + + + + + + + + diff --git a/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs new file mode 100644 index 00000000..a6440685 --- /dev/null +++ b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs @@ -0,0 +1,90 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +internal 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 nint OpusGetVersionString(); + + [LibraryImport("libzstd", 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()}"); + Console.WriteLine($"Opus Version String: {Marshal.PtrToStringAnsi(NativeProbes.OpusGetVersionString())}"); + Console.WriteLine($"Zstd Version Number: {NativeProbes.ZstdVersionNumber()}"); + + var cwd = Path.GetFullPath(Directory.GetCurrentDirectory()); + Console.WriteLine($"Current working directory: {cwd}"); + + string[] libraries = new[] { "libdave", "libsodium", "opus", "libzstd" }; + var process = Process.GetCurrentProcess(); + var modules = process.Modules; + + bool allUnderCwd = true; + + foreach (var lib in libraries) + { + ProcessModule found = null; + foreach (ProcessModule m in modules) + { + if (!string.IsNullOrEmpty(m.FileName) && m.FileName.IndexOf(lib, StringComparison.OrdinalIgnoreCase) >= 0) + { + found = m; + break; + } + if (!string.IsNullOrEmpty(m.ModuleName) && m.ModuleName.IndexOf(lib, StringComparison.OrdinalIgnoreCase) >= 0) + { + found = m; + break; + } + } + + if (found == null) + { + Console.WriteLine($"Library '{lib}' not found among loaded modules."); + allUnderCwd = false; + continue; + } + + Console.WriteLine($"{lib} loaded from: {found.FileName}"); + if (!IsUnderDirectory(found.FileName, cwd)) + { + Console.WriteLine($"Library '{lib}' is not under current working directory."); + allUnderCwd = false; + } + } + + Environment.Exit(allUnderCwd ? 0 : 1); + } + + private static bool IsUnderDirectory(string path, string directory) + { + try + { + var fullPath = Path.GetFullPath(path); + var fullDir = Path.GetFullPath(directory).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; + var comparison = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + return fullPath.StartsWith(fullDir, comparison); + } + catch + { + return false; + } + } +} diff --git a/Tests/NetCord.Natives.Tests/NativesBuildTests.cs b/Tests/NetCord.Natives.Tests/NativesBuildTests.cs new file mode 100644 index 00000000..b47e24bb --- /dev/null +++ b/Tests/NetCord.Natives.Tests/NativesBuildTests.cs @@ -0,0 +1,134 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace NetCord.Natives.Tests; + +[TestClass] +public class NativesBuildTests +{ + // MSTest automatically injects this property + public TestContext TestContext { get; set; } + + [TestMethod] + [DataRow("libdave")] + [DataRow("libsodium")] + [DataRow("opus")] + [DataRow("zstd")] + public void NativesLoaded(string libName) + { + try + { + NativeLibrary.Load(libName); + } + catch (Exception ex) + { + Assert.Fail($"Failed to load library '{libName}': {ex}"); + } + } + + [TestMethod] + [DataRow("libdave", "NetCord.Gateway.Voice.Dave")] + [DataRow("libsodium", "NetCord.Gateway.Voice.Encryption.XChaCha20Poly1305")] + [DataRow("opus", "NetCord.Gateway.Voice.Opus")] + [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}'."); + + 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); + } + } + + [TestMethod] + [DataRow("libdave;libsodium;opus;zstd")] + public void NativeAotStaticLinking(string libName) + { + try + { + // get NetCordNativesDir from AssemblyMetadata attribute + var assembly = typeof(NativesBuildTests).Assembly; + var nativesDir = assembly.GetCustomAttribute()?.Value; + Assert.IsNotNull(nativesDir, "NetCordNativesDir 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("-p:Configuration=Release"); + buildProcess.StartInfo.ArgumentList.Add("-p:RuntimeIdentifier=" + RuntimeInformation.RuntimeIdentifier); + buildProcess.StartInfo.ArgumentList.Add($"-p:NetCordNativesDir={nativesDir}"); + buildProcess.StartInfo.ArgumentList.Add($"-p:NetCordDirectPInvoke=\"{libName}\""); + + TestContext.WriteLine($"Building Native AoT app: 'dotnet {buildProcess.StartInfo.ArgumentList.Aggregate((a, b) => $"{a} {b}")}'"); + + buildProcess.StartInfo.WorkingDirectory = Path.Combine(AppContext.BaseDirectory, "Assets", "NativeAotApp"); + 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(AppContext.BaseDirectory, "Assets", "NativeAotApp", + "bin", "release", RuntimeInformation.RuntimeIdentifier, "publish", "NativeAotApp.exe"); + + 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) + { + Assert.Fail($"Failed to statically link '{libName}' for Native Ahead-of-Time (AoT) compilation: {ex}"); + } + } +} diff --git a/Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj b/Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj new file mode 100644 index 00000000..9ddd5a5b --- /dev/null +++ b/Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj @@ -0,0 +1,44 @@ + + + + net10.0 + enable + enable + + false + true + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + ..\..\NetCord.Natives\ + + + + + + <_Parameter1>NetCordNativesDir + <_Parameter2>$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\$(NetCordNativesDir)')) + + + + + + + + From 247d363019527eecf1a327766b4657172b600b89 Mon Sep 17 00:00:00 2001 From: Haves Irfan <20160532+ha-ves@users.noreply.github.com> Date: Tue, 5 May 2026 03:00:19 +0900 Subject: [PATCH 04/10] * Refactor NetCord.Natives.csproj for improved build configuration * Add MSVC builtin add overflow patch for libdave * Add vcpkg-non-windows.targets for cross-platform support * Update libdave and mlspp portfiles with build fixes * Update build-natives workflow and vcpkg.json versions * Fix vcpkg dependency resolution for multi-platform builds --- .github/workflows/build-natives.yml | 10 +- NetCord.Natives/NetCord.Natives.csproj | 186 +++++++++--------- .../fix-msvc-builtin-add-overflow.patch | 24 +++ .../natives-ports/libdave/portfile.cmake | 5 + .../natives-ports/mlspp/portfile.cmake | 4 + NetCord.Natives/vcpkg-non-windows.targets | 48 +++++ NetCord.Natives/vcpkg.json | 9 +- 7 files changed, 187 insertions(+), 99 deletions(-) create mode 100644 NetCord.Natives/natives-ports/libdave/fix-msvc-builtin-add-overflow.patch create mode 100644 NetCord.Natives/vcpkg-non-windows.targets diff --git a/.github/workflows/build-natives.yml b/.github/workflows/build-natives.yml index 91d27e9f..26e8a3ba 100644 --- a/.github/workflows/build-natives.yml +++ b/.github/workflows/build-natives.yml @@ -71,7 +71,11 @@ jobs: } if (-not (Test-Path $vcpkgRoot)) { - throw "vcpkg installation root not found." + # Bootstrap vcpkg + if ($IsWindows) { + & "$vcpkgRoot/bootstrap-vcpkg.bat" -disableMetrics + } else { + & "$vcpkgRoot/bootstrap-vcpkg.sh" -disableMetrics } "VCPKG_ROOT=$vcpkgRoot" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 @@ -85,7 +89,7 @@ jobs: - name: Build native outputs for ${{ matrix.rid }} shell: pwsh run: | - dotnet build NetCord.Natives/NetCord.Natives.csproj -c Release --no-restore -p:GeneratePackageOnBuild=false -p:VcpkgMsbuildPath=$env:VcpkgMsbuildPath -p:VcpkgOSTarget=${{ matrix.vcpkg-os-target }} -p:VcpkgPlatformTarget=${{ matrix.vcpkg-platform-target }} + dotnet build NetCord.Natives/NetCord.Natives.csproj -c Release --no-restore -p:GeneratePackageOnBuild=false -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgMsbuildPath=$env:VcpkgMsbuildPath -p:VcpkgOSTarget=${{ matrix.vcpkg-os-target }} -p:VcpkgPlatformTarget=${{ matrix.vcpkg-platform-target }} - name: List artifacts shell: pwsh @@ -118,7 +122,7 @@ jobs: - name: Pack combined nupkg shell: pwsh run: | - dotnet pack NetCord.Natives/NetCord.Natives.csproj -c Release --no-build --no-restore -p:VcpkgMsbuildPath=$env:VcpkgMsbuildPath -p:NativeArtifactsRoot=$env:NativeArtifactsRoot -p:NativeLicenseRoot=$env:NativeLicenseRoot -o artifacts/package + dotnet pack NetCord.Natives/NetCord.Natives.csproj -c Release --no-build --no-restore -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgMsbuildPath=$env:VcpkgMsbuildPath -p:NativeArtifactsRoot=$env:NativeArtifactsRoot -p:NativeLicenseRoot=$env:NativeLicenseRoot -o artifacts/package - name: Upload nupkg uses: actions/upload-artifact@v4 diff --git a/NetCord.Natives/NetCord.Natives.csproj b/NetCord.Natives/NetCord.Natives.csproj index 0ea591d8..67dbfe4f 100644 --- a/NetCord.Natives/NetCord.Natives.csproj +++ b/NetCord.Natives/NetCord.Natives.csproj @@ -19,7 +19,12 @@ debug - vcpkg/scripts/buildsystems/msbuild/vcpkg + bin + + $(VCPKG_ROOT) + $(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg + + Release true windows @@ -28,71 +33,104 @@ $(VcpkgOSTarget) win - + + + false + x64 $(Platform) - bin - - $(NativeArtifactsRoot)\$(VcpkgBuildOS)-$(VcpkgPlatformTarget)\natives + $(VcpkgArtifactsRoot)\$(VcpkgBuildOS)-$(VcpkgPlatformTarget)\natives + $(VcpkgInstalledDirBase) $(VcpkgInstalledDir)-static - $(VcpkgInstalledDir)\$(VcpkgPlatformTarget)-$(VcpkgOSTarget) - $(VcpkgInstalledDir)\$(VcpkgPlatformTarget)-$(VcpkgOSTarget)-static - - <_BuildNativeStaticOutputPath>$(VcpkgInstalledDir)\$(VcpkgPlatformTarget)-$(VcpkgOSTarget)-static + $(VcpkgInstalledDirBase)-static\$(VcpkgPlatformTarget)-$(VcpkgOSTarget) + $(VcpkgOutputPath_Static)-static <_VcpkgStatusFile>$(VcpkgInstalledDir)\vcpkg\status - - - - + + + + + - + + + <_VcpkgRelatedFile Include="native-ports\**" /> - + - <_NetCordStaticFullPath Include="$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\$(_BuildNativeStaticOutputPath)'))" /> + <_StaticFullPath Include="$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\$(VcpkgOutputPath_Static)'))" /> - + + + + + + + $(VcpkgPlatformTarget)-$(VcpkgOSTarget) + $(VcpkgTriplet)-dynamic + + <_ZVcpkgMSBuildStampFile>$(_ZVcpkgInstalledDir).msbuildstamp-$(VcpkgTriplet).$(_ZVcpkgHostTripletSuffix)stamp + + + $(VcpkgInstalledDir)\$(VcpkgTriplet) + - - - - - - - - - + + <_LibArchs Include="x64;arm64"> + %(NativeLibSet.Identity) + + <_BuiltRid Include="win-%(_LibArchs.Identity)"> + %(_LibArchs.Identity)-windows\bin\%(_LibArchs.LibName) + %(_LibArchs.Identity)-windows-static\lib\%(_LibArchs.LibName) + dll + + <_BuiltRid Include="linux-%(_LibArchs.Identity)"> + %(_LibArchs.Identity)-linux-dynamic\lib\lib%(_LibArchs.LibName) + %(_LibArchs.Identity)-linux\lib\lib%(_LibArchs.LibName) + so + + <_BuiltRid Include="osx-%(_LibArchs.Identity)"> + %(_LibArchs.Identity)-osx-dynamic\lib\lib%(_LibArchs.LibName) + %(_LibArchs.Identity)-osx\lib\lib%(_LibArchs.LibName) + dylib + + <_BuiltRid Remove="%(Identity)" Condition="'$(CI)' != 'true' and '%(Identity)' != '$(VcpkgBuildOS)-$(VcpkgPlatformTarget)'" /> + - - + + + - - - <_NativeLicenseSource Include="$(BuildNativeOutputPath)\share\%(AllowedNativesLicenses.Identity)\copyright" - Condition="Exists('$(BuildNativeOutputPath)\share\%(AllowedNativesLicenses.Identity)\copyright')"> - $(BuildNativeOutputPath)\licenses\%(AllowedNativesLicenses.Identity)_LICENSE.txt + Properties="VcpkgUseStatic=true;GeneratePackageOnBuild=false" + Condition="'$(VcpkgUseStatic)' != 'true'" /> + + + <_NativeLicenseSource Include="$(VcpkgOutputPath)\share\%(NativeLibLic.Identity)\copyright" + Condition="Exists('$(VcpkgOutputPath)\share\%(NativeLibLic.Identity)\copyright')"> + $(VcpkgOutputPath)\licenses\%(NativeLibLic.Identity)_LICENSE.txt + SkipUnchangedFiles="true" Condition="'$(VcpkgUseStatic)' != 'true'" /> - + @@ -106,27 +144,27 @@ - + <_VcpkgPkg Include="@(NativeLibLic)"> $([System.Text.RegularExpressions.Regex]::Match('$(StatusText)', 'Package: %(Identity)\n(?:.*\n)*?Version: (.*?)\n').Groups[1].Value) - + - - <_Parameter1>%(PackageWithVersion.Identity) - <_Parameter2>%(PackageWithVersion.Version) + + <_Parameter1>%(_VcpkgPkg.Identity) + <_Parameter2>%(_VcpkgPkg.Version) - + - + - + %(Filename)%(Extension) @@ -136,67 +174,25 @@ - + $(PackageId).$(VcpkgBuildOS)-$(VcpkgPlatformTarget) - - - runtimes\$(VcpkgBuildOS)-$(VcpkgPlatformTarget)\native\ - - - staticlibs\$(VcpkgBuildOS)-$(VcpkgPlatformTarget)\ - - - - - <_BuiltTriplets Include="x64-windows"> - win-x64 - x64-windows\bin\%(AllowedNativeLibrary.Identity) - x64-windows-static\lib\%(AllowedNativeLibrary.Identity) - - <_BuiltTriplets Include="arm64-windows"> - win-arm64 - arm64-windows\bin\%(AllowedNativeLibrary.Identity) - arm64-windows-static\lib\%(AllowedNativeLibrary.Identity) - - <_BuiltTriplets Include="x64-linux"> - linux-x64 - x64-linux\bin\%(AllowedNativeLibrary.Identity) - x64-linux-static\lib\%(AllowedNativeLibrary.Identity) - - <_BuiltTriplets Include="arm64-linux"> - linux-arm64 - arm64-linux\bin\%(AllowedNativeLibrary.Identity) - arm64-linux-static\lib\%(AllowedNativeLibrary.Identity) - - <_BuiltTriplets Include="x64-osx"> - osx-x64 - x64-osx\bin\%(AllowedNativeLibrary.Identity) - x64-osx-static\lib\%(AllowedNativeLibrary.Identity) - - <_BuiltTriplets Include="arm64-osx"> - osx-arm64 - arm64-osx\bin\%(AllowedNativeLibrary.Identity) - arm64-osx-static\lib\%(AllowedNativeLibrary.Identity) - - - - runtimes\%(_BuiltTriplets.RuntimeId)\native\ + + + runtimes\%(_BuiltRid.Identity)\native\ - - staticlibs\%(_BuiltTriplets.RuntimeId)\ + + staticlibs\%(_BuiltRid.Identity)\ - - - + true - + true licenses\ diff --git a/NetCord.Natives/natives-ports/libdave/fix-msvc-builtin-add-overflow.patch b/NetCord.Natives/natives-ports/libdave/fix-msvc-builtin-add-overflow.patch new file mode 100644 index 00000000..f6d3360a --- /dev/null +++ b/NetCord.Natives/natives-ports/libdave/fix-msvc-builtin-add-overflow.patch @@ -0,0 +1,24 @@ +diff --git a/cpp/src/frame_processors.cpp b/cpp/src/frame_processors.cpp +index 37c8d70..5f0460c 100644 +--- a/cpp/src/frame_processors.cpp ++++ b/cpp/src/frame_processors.cpp +@@ -13,6 +13,9 @@ + + #if defined(_MSC_VER) + #include ++#if defined(_M_ARM64) ++#include ++#endif + #endif + + namespace discord { +@@ -25,6 +28,9 @@ std::pair OverflowAdd(size_t a, size_t b) + bool didOverflow = _addcarry_u64(0, a, b, &res); + #elif defined(_MSC_VER) && defined(_M_IX86) + bool didOverflow = _addcarry_u32(0, a, b, &res); ++#elif defined(_MSC_VER) && defined(_M_ARM64) ++ res = a + b; ++ bool didOverflow = res < a; + #else + bool didOverflow = __builtin_add_overflow(a, b, &res); + #endif diff --git a/NetCord.Natives/natives-ports/libdave/portfile.cmake b/NetCord.Natives/natives-ports/libdave/portfile.cmake index a001f80e..4f7517e7 100644 --- a/NetCord.Natives/natives-ports/libdave/portfile.cmake +++ b/NetCord.Natives/natives-ports/libdave/portfile.cmake @@ -4,6 +4,7 @@ vcpkg_from_github( REF "${VERSION}" SHA512 78b4e5b8ddc6397775d403465e0da770ec7905d7913546b3aec161baf4478443e554f0ae7bd012af8bfd308639be2601d46da22c02aff2b756ff91878f1fc843 HEAD_REF main + PATCHES fix-msvc-builtin-add-overflow.patch ) vcpkg_cmake_configure( @@ -25,3 +26,7 @@ file(INSTALL DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright ) + +# Remove redundant debug directories to comply with vcpkg policy +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share") diff --git a/NetCord.Natives/natives-ports/mlspp/portfile.cmake b/NetCord.Natives/natives-ports/mlspp/portfile.cmake index 66dd943c..2ca219e3 100644 --- a/NetCord.Natives/natives-ports/mlspp/portfile.cmake +++ b/NetCord.Natives/natives-ports/mlspp/portfile.cmake @@ -27,3 +27,7 @@ vcpkg_cmake_install() vcpkg_copy_pdbs() vcpkg_cmake_config_fixup(PACKAGE_NAME "MLSPP" CONFIG_PATH "share/MLSPP") + +# Remove redundant debug directories to comply with vcpkg policy +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share") diff --git a/NetCord.Natives/vcpkg-non-windows.targets b/NetCord.Natives/vcpkg-non-windows.targets new file mode 100644 index 00000000..134e697a --- /dev/null +++ b/NetCord.Natives/vcpkg-non-windows.targets @@ -0,0 +1,48 @@ + + + + <_ZVcpkgInstallManifestDependenciesInputs Include="$(_ZVcpkgManifestFileLocation)"/> + <_ZVcpkgInstallManifestDependenciesInputs Include="$(_ZVcpkgConfigurationFileLocation)" + Condition="Exists('$(_ZVcpkgConfigurationFileLocation)')"/> + + + + + + + <_ZVcpkgTLogFileLocation>$(TLogLocation)VcpkgInstallManifest$(VcpkgTriplet).$(_ZVcpkgHostTripletSuffix)read.1u.tlog + + <_ZVcpkgExecutable>$(_ZVcpkgRoot)vcpkg + + + + + + + + + + + + + + + + + + + diff --git a/NetCord.Natives/vcpkg.json b/NetCord.Natives/vcpkg.json index 1712ed4f..a3af5a40 100644 --- a/NetCord.Natives/vcpkg.json +++ b/NetCord.Natives/vcpkg.json @@ -9,5 +9,12 @@ "overlay-ports": [ "natives-ports" ] - } + }, + "builtin-baseline": "d07689ef165f033de5c0710e4f67c193a85373e1", + "overrides": [ + { + "name": "openssl", + "version": "3.0.7" + } + ] } From aa317d0aa4c4d2c3a5ffe4af3ac456119beb8b2c Mon Sep 17 00:00:00 2001 From: Haves Irfan <20160532+ha-ves@users.noreply.github.com> Date: Tue, 5 May 2026 21:18:49 +0900 Subject: [PATCH 05/10] * Refactor native library handling and packaging * Add dynamic library exclusion for AOT builds * Add NativeAotApp test project --- NetCord.Natives/NetCord.Natives.csproj | 32 +++++++++---------- NetCord.Natives/NetCord.Natives.local.targets | 12 +++++-- NetCord.Natives/NetCord.Natives.props | 9 ++++++ NetCord.Natives/NetCord.Natives.targets | 28 ++++++++++++++-- NetCord.slnx | 1 + .../Assets/NativeAotApp/NativeAotApp.csproj | 20 ++++++------ .../Assets/NativeAotApp/Program.cs | 6 ++-- 7 files changed, 75 insertions(+), 33 deletions(-) diff --git a/NetCord.Natives/NetCord.Natives.csproj b/NetCord.Natives/NetCord.Natives.csproj index 67dbfe4f..216d209f 100644 --- a/NetCord.Natives/NetCord.Natives.csproj +++ b/NetCord.Natives/NetCord.Natives.csproj @@ -117,16 +117,6 @@ - - - <_NativeLicenseSource Include="$(VcpkgOutputPath)\share\%(NativeLibLic.Identity)\copyright" - Condition="Exists('$(VcpkgOutputPath)\share\%(NativeLibLic.Identity)\copyright')"> - $(VcpkgOutputPath)\licenses\%(NativeLibLic.Identity)_LICENSE.txt - - - - - + - - + <_CopyOutput Include="%(NativesRuntime.FullPath)" Condition="Exists('%(NativesRuntime.Identity)')"> %(Filename)%(Extension) PreserveNewest - false - + + + + <_NativeLicenseSource Include="$(VcpkgOutputPath_Static)\share\%(NativeLibLic.Identity)\copyright" + Condition="Exists('$(VcpkgOutputPath_Static)\share\%(NativeLibLic.Identity)\copyright')"> + $(VcpkgOutputPath_Static)\licenses\%(NativeLibLic.Identity)_LICENSE.txt + + + + + $(PackageId).$(VcpkgBuildOS)-$(VcpkgPlatformTarget) @@ -192,7 +192,7 @@ true - + true licenses\ diff --git a/NetCord.Natives/NetCord.Natives.local.targets b/NetCord.Natives/NetCord.Natives.local.targets index 7a542e0e..3f730cc0 100644 --- a/NetCord.Natives/NetCord.Natives.local.targets +++ b/NetCord.Natives/NetCord.Natives.local.targets @@ -5,12 +5,20 @@ + Targets="GetStaticBuildDir" + Properties="CI=false"> + + + + - @(_NetCordStaticDir->'%(Identity)')\lib + %(_NetCordStaticDir.Identity)\lib + $([System.IO.Path]::GetDirectoryName('%(_NetCordRuntimesDir.FullPath)')) diff --git a/NetCord.Natives/NetCord.Natives.props b/NetCord.Natives/NetCord.Natives.props index 10bbd708..c1b6ee94 100644 --- a/NetCord.Natives/NetCord.Natives.props +++ b/NetCord.Natives/NetCord.Natives.props @@ -1,6 +1,15 @@ + + + + $(MSBuildThisFileDirectory)..\runtimes\$(RuntimeIdentifier) $(MSBuildThisFileDirectory)..\staticlibs\$(RuntimeIdentifier) diff --git a/NetCord.Natives/NetCord.Natives.targets b/NetCord.Natives/NetCord.Natives.targets index a9ab04c5..93d835ee 100644 --- a/NetCord.Natives/NetCord.Natives.targets +++ b/NetCord.Natives/NetCord.Natives.targets @@ -12,11 +12,35 @@ <_NetCordStaticLink Include="zstd" Condition="'%(DirectPInvoke.Identity)' == 'zstd'" /> - + + - + + + + + <_NetCordExcluded Include="$(NetCordExcludeNatives)" + Condition="'$(NetCordExcludeNatives)' != ''" /> + <_NetCordExcluded Include="libcrypto" + Condition="$(NetCordExcludeNatives.Contains('libdave'))" /> + + <_NetCordExcluded Include="@(_NetCordStaticLink)" + Condition="'$(PublishAot)' == 'true' and '%(_NetCordStaticLink.Identity)' != ''"/> + + <_RunExclude Include="$(NetCordNativesRuntimesDir)\**\native\%(_NetCordExcluded.Identity)*.*" + Condition="'@(_NetCordExcluded.Identity)' != ''" /> + <_RunExclude Include="$(NetCordNativesRuntimesDir)\**\native\lib%(_NetCordExcluded.Identity)*.*" + Condition="'@(_NetCordExcluded.Identity)' != ''" /> + + + + + + diff --git a/NetCord.slnx b/NetCord.slnx index 08ac717d..2338dbc5 100644 --- a/NetCord.slnx +++ b/NetCord.slnx @@ -85,6 +85,7 @@ + diff --git a/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj index 05c22e53..8209957b 100644 --- a/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj +++ b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj @@ -1,4 +1,4 @@ - +ο»Ώ Exe @@ -8,17 +8,17 @@ true true + true + false + + ..\..\..\..\NetCord.Natives - - - - - - + + + - - - + + diff --git a/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs index a6440685..111e3a69 100644 --- a/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs +++ b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs @@ -14,7 +14,7 @@ internal static partial class NativeProbes [LibraryImport("opus", EntryPoint = "opus_get_version_string")] internal static partial nint OpusGetVersionString(); - [LibraryImport("libzstd", EntryPoint = "ZSTD_versionNumber")] + [LibraryImport("zstd", EntryPoint = "ZSTD_versionNumber")] internal static partial uint ZstdVersionNumber(); } @@ -32,7 +32,7 @@ private static void Main() var cwd = Path.GetFullPath(Directory.GetCurrentDirectory()); Console.WriteLine($"Current working directory: {cwd}"); - string[] libraries = new[] { "libdave", "libsodium", "opus", "libzstd" }; + string[] libraries = new[] { "libdave", "libsodium", "opus", "zstd" }; var process = Process.GetCurrentProcess(); var modules = process.Modules; @@ -40,7 +40,7 @@ private static void Main() foreach (var lib in libraries) { - ProcessModule found = null; + ProcessModule? found = null; foreach (ProcessModule m in modules) { if (!string.IsNullOrEmpty(m.FileName) && m.FileName.IndexOf(lib, StringComparison.OrdinalIgnoreCase) >= 0) From da37fffb97e4a8e39750e87a353567d7e048480f Mon Sep 17 00:00:00 2001 From: Haves Irfan <20160532+ha-ves@users.noreply.github.com> Date: Wed, 6 May 2026 03:58:37 +0900 Subject: [PATCH 06/10] CI finished & fix typos & guarding --- .github/workflows/build-natives.yml | 119 ++++++++++++++++++++----- NetCord.Natives/NetCord.Natives.csproj | 4 +- 2 files changed, 99 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build-natives.yml b/.github/workflows/build-natives.yml index 26e8a3ba..27e5fd3e 100644 --- a/.github/workflows/build-natives.yml +++ b/.github/workflows/build-natives.yml @@ -48,15 +48,19 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6.0.2 - name: Cache vcpkg installs - uses: actions/cache@v4 + uses: actions/cache@v5.0.5 with: path: | .vcpkg-cache .vcpkg-downloads - key: vcpkg-${{ runner.os }}-${{ matrix.rid }}-${{ hashFiles('NetCord.Natives/vcpkg.json') }} + key: vcpkg-${{ runner.os }}-${{ matrix.rid }}- + ${{ hashFiles( + 'NetCord.Natives/vcpkg.json', + 'NetCord.Natives/natives-ports/**' + ) }} restore-keys: | vcpkg-${{ runner.os }}-${{ matrix.rid }}- vcpkg-${{ runner.os }}- @@ -64,68 +68,139 @@ jobs: - name: Resolve vcpkg from runner image shell: pwsh run: | + # Ensure vcpkg cache and downloads directories exist + New-Item -ItemType Directory -Path $env:VCPKG_BINARY_SOURCES.Split(',')[1] -Force | Out-Null + New-Item -ItemType Directory -Path $env:VCPKG_DOWNLOADS -Force | Out-Null + $vcpkgRoot = $env:VCPKG_INSTALLATION_ROOT if (-not $vcpkgRoot) { $vcpkgCommand = Get-Command vcpkg -ErrorAction Stop $vcpkgRoot = Split-Path -Parent $vcpkgCommand.Source } - if (-not (Test-Path $vcpkgRoot)) { - # Bootstrap vcpkg - if ($IsWindows) { - & "$vcpkgRoot/bootstrap-vcpkg.bat" -disableMetrics - } else { - & "$vcpkgRoot/bootstrap-vcpkg.sh" -disableMetrics + if (Test-Path $vcpkgRoot) { + # Bootstrap vcpkg + if ($IsWindows) { + & "$vcpkgRoot/bootstrap-vcpkg.bat" -disableMetrics + } else { + & "$vcpkgRoot/bootstrap-vcpkg.sh" -disableMetrics + } } "VCPKG_ROOT=$vcpkgRoot" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 - "VcpkgMsbuildPath=$vcpkgRoot/scripts/buildsystems/msbuild/vcpkg" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + + - name: Install native build tools (macOS) + if: runner.os == 'macOS' + shell: bash + run: | + brew update + brew install autoconf automake libtool pkg-config + + - name: Install ARM64 cross-compiler (linux-arm64) + if: matrix.rid == 'linux-arm64' + shell: bash + run: | + sudo apt-get update + sudo apt-get install -yq gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev-arm64-cross - name: Restore shell: pwsh run: | - dotnet restore NetCord.Natives/NetCord.Natives.csproj -p:VcpkgMsbuildPath=$env:VcpkgMsbuildPath -p:VcpkgOSTarget=${{ matrix.vcpkg-os-target }} -p:VcpkgPlatformTarget=${{ matrix.vcpkg-platform-target }} + dotnet restore NetCord.Natives/NetCord.Natives.csproj -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgOSTarget=${{ matrix.vcpkg-os-target }} -p:VcpkgPlatformTarget=${{ matrix.vcpkg-platform-target }} - name: Build native outputs for ${{ matrix.rid }} shell: pwsh run: | - dotnet build NetCord.Natives/NetCord.Natives.csproj -c Release --no-restore -p:GeneratePackageOnBuild=false -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgMsbuildPath=$env:VcpkgMsbuildPath -p:VcpkgOSTarget=${{ matrix.vcpkg-os-target }} -p:VcpkgPlatformTarget=${{ matrix.vcpkg-platform-target }} + dotnet build NetCord.Natives/NetCord.Natives.csproj -c Release --no-restore -p:GeneratePackageOnBuild=false -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgOSTarget=${{ matrix.vcpkg-os-target }} -p:VcpkgPlatformTarget=${{ matrix.vcpkg-platform-target }} - - name: List artifacts + - name: List built for ${{ matrix.rid }} shell: pwsh run: | - Get-ChildItem -Recurse NetCord.Natives/bin | Select-Object FullName + $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) { + Write-Host "Contents of: $path" + Get-ChildItem -Path $path -Recurse -Force | Select-Object FullName, Mode, Length, LastWriteTime + } else { + Write-Host "[Info]Not found: $path" + } + } + } + } - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7.0.1 with: name: ${{ matrix.rid }} - path: NetCord.Natives/bin + path: NetCord.Natives/bin/${{ matrix.rid }} pack-natives: needs: build-natives runs-on: ubuntu-latest env: CI: true - NativeArtifactsRoot: ${{ github.workspace }}/artifacts + VCPKG_BINARY_SOURCES: clear;files,${{ github.workspace }}/.vcpkg-cache,readwrite + VCPKG_DOWNLOADS: ${{ github.workspace }}/.vcpkg-downloads + VcpkgArtifactsRoot: ${{ github.workspace }}/artifacts steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6.0.2 - name: Download native artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8.0.1 with: path: artifacts merge-multiple: false + - name: Resolve vcpkg from runner image + shell: pwsh + run: | + # Ensure vcpkg cache and downloads directories exist + New-Item -ItemType Directory -Path $env:VCPKG_BINARY_SOURCES.Split(',')[1] -Force | Out-Null + New-Item -ItemType Directory -Path $env:VCPKG_DOWNLOADS -Force | Out-Null + + $vcpkgRoot = $env:VCPKG_INSTALLATION_ROOT + if (-not $vcpkgRoot) { + $vcpkgCommand = Get-Command vcpkg -ErrorAction Stop + $vcpkgRoot = Split-Path -Parent $vcpkgCommand.Source + } + + if (Test-Path $vcpkgRoot) { + # Bootstrap vcpkg + if ($IsWindows) { + & "$vcpkgRoot/bootstrap-vcpkg.bat" -disableMetrics + } else { + & "$vcpkgRoot/bootstrap-vcpkg.sh" -disableMetrics + } + } + + "VCPKG_ROOT=$vcpkgRoot" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + + - name: Restore (required for pack) + shell: pwsh + run: | + dotnet restore NetCord.Natives/NetCord.Natives.csproj -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgEnabled=false + + - name: Build cross-platform .NET (required for pack) + shell: pwsh + run: | + dotnet build NetCord.Natives/NetCord.Natives.csproj -c Release --no-restore -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgEnabled=false + - name: Pack combined nupkg shell: pwsh run: | - dotnet pack NetCord.Natives/NetCord.Natives.csproj -c Release --no-build --no-restore -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgMsbuildPath=$env:VcpkgMsbuildPath -p:NativeArtifactsRoot=$env:NativeArtifactsRoot -p:NativeLicenseRoot=$env:NativeLicenseRoot -o artifacts/package + dotnet pack NetCord.Natives/NetCord.Natives.csproj -c Release --no-build --no-restore -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgArtifactsRoot=$env:VcpkgArtifactsRoot -o $env:VcpkgArtifactsRoot/package - name: Upload nupkg - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7.0.1 with: name: NetCord.Natives.Package path: artifacts/package/*.nupkg @@ -136,7 +211,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download nupkg - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8.0.1 with: name: NetCord.Natives.Package path: artifacts/package diff --git a/NetCord.Natives/NetCord.Natives.csproj b/NetCord.Natives/NetCord.Natives.csproj index 216d209f..621f5065 100644 --- a/NetCord.Natives/NetCord.Natives.csproj +++ b/NetCord.Natives/NetCord.Natives.csproj @@ -55,7 +55,7 @@ - + @@ -174,7 +174,7 @@ + SkipUnchangedFiles="true" Condition="'@(_NativeLicenseSource)' != ''" /> $(PackageId).$(VcpkgBuildOS)-$(VcpkgPlatformTarget) From a8834800fe4c691570e3f26457cb5635c1418866 Mon Sep 17 00:00:00 2001 From: Haves Irfan <20160532+ha-ves@users.noreply.github.com> Date: Wed, 6 May 2026 04:10:51 +0900 Subject: [PATCH 07/10] solution build fix --- .../Assets/NativeAotApp/NativeAotApp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj index 8209957b..2eade721 100644 --- a/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj +++ b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj @@ -11,7 +11,7 @@ true false - ..\..\..\..\NetCord.Natives + ..\..\..\..\NetCord.Natives From 498226a20a00b31e9c677afcafa74ce53ebc7ce1 Mon Sep 17 00:00:00 2001 From: Haves Irfan <20160532+ha-ves@users.noreply.github.com> Date: Wed, 6 May 2026 06:36:19 +0900 Subject: [PATCH 08/10] separate solution for natives * natives conditionals fix * fix nativeaot test structure * add natives tests to build workflow --- .github/workflows/build-natives.yml | 9 ++- .github/workflows/build.yml | 6 +- NetCord.Natives.slnx | 7 +++ NetCord.Natives/NetCord.Natives.csproj | 12 ++-- NetCord.Natives/NetCord.Natives.local.targets | 5 +- NetCord.Natives/NetCord.Natives.targets | 4 +- NetCord.slnx | 4 +- .../Assets/NativeAotApp/NativeAotApp.csproj | 1 + .../Assets/NativeAotApp/Program.cs | 60 +------------------ .../NativesBuildTests.cs | 31 ++++++++-- .../NetCord.Natives.Tests.csproj | 8 +++ 11 files changed, 67 insertions(+), 80 deletions(-) create mode 100644 NetCord.Natives.slnx diff --git a/.github/workflows/build-natives.yml b/.github/workflows/build-natives.yml index 27e5fd3e..c56bd99c 100644 --- a/.github/workflows/build-natives.yml +++ b/.github/workflows/build-natives.yml @@ -1,4 +1,4 @@ -name: Build NetCord.Natives +name: Build, Test, and Package NetCord.Natives on: workflow_dispatch: {} @@ -11,7 +11,7 @@ on: - "[0-9]+.[0-9]+.[0-9]+-*" jobs: - build-natives: + build-test-natives: runs-on: ${{ matrix.runs-on }} env: CI: true @@ -113,6 +113,11 @@ jobs: run: | dotnet build NetCord.Natives/NetCord.Natives.csproj -c Release --no-restore -p:GeneratePackageOnBuild=false -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgOSTarget=${{ matrix.vcpkg-os-target }} -p:VcpkgPlatformTarget=${{ matrix.vcpkg-platform-target }} + - name: Test native outputs for ${{ matrix.rid }} + shell: pwsh + run: | + dotnet test NetCord.Natives.Tests/NetCord.Natives.Tests.csproj -c Release -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgOSTarget=${{ matrix.vcpkg-os-target }} -p:VcpkgPlatformTarget=${{ matrix.vcpkg-platform-target }} --verbosity normal + - name: List built for ${{ matrix.rid }} shell: pwsh run: | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 40c35ec0..89f48496 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,11 +38,13 @@ jobs: - name: Build shell: *dev-shell - run: dotnet build -c Release --no-restore --warnaserror + run: | + dotnet build NetCord.slnx -c Release --no-restore --warnaserror + dotnet build NetCord.Natives.slnx -c Release --no-restore --warnaserror -p:VcpkgRoot=$VCPKG_INSTALLATION_ROOT -p:VcpkgEnabled=false - name: Test shell: *dev-shell - run: dotnet test -c Release --no-build --verbosity normal + run: dotnet test NetCord.slnx -c Release --no-build --verbosity normal - name: Pack Packages shell: *dev-shell diff --git a/NetCord.Natives.slnx b/NetCord.Natives.slnx new file mode 100644 index 00000000..1f71c569 --- /dev/null +++ b/NetCord.Natives.slnx @@ -0,0 +1,7 @@ + + + + + + + diff --git a/NetCord.Natives/NetCord.Natives.csproj b/NetCord.Natives/NetCord.Natives.csproj index 621f5065..735f1e98 100644 --- a/NetCord.Natives/NetCord.Natives.csproj +++ b/NetCord.Natives/NetCord.Natives.csproj @@ -76,6 +76,10 @@ Condition="'$(VcpkgBuildOS)' != 'win' and Exists('vcpkg-non-windows.targets')" /> + + + $(VcpkgPlatformTarget)-$(VcpkgOSTarget) $(VcpkgTriplet)-dynamic @@ -111,7 +115,7 @@ - + - <_CopyOutput Include="%(NativesRuntime.FullPath)" Condition="Exists('%(NativesRuntime.Identity)')"> - %(Filename)%(Extension) + <_CopyOutput Include="%(NativesRuntime.FullPath)"> + %(NativesRuntime.Filename)%(NativesRuntime.Extension) PreserveNewest - + diff --git a/NetCord.Natives/NetCord.Natives.local.targets b/NetCord.Natives/NetCord.Natives.local.targets index 3f730cc0..06e3c54d 100644 --- a/NetCord.Natives/NetCord.Natives.local.targets +++ b/NetCord.Natives/NetCord.Natives.local.targets @@ -1,6 +1,6 @@ - + @@ -18,7 +18,8 @@ %(_NetCordStaticDir.Identity)\lib - $([System.IO.Path]::GetDirectoryName('%(_NetCordRuntimesDir.FullPath)')) + $([System.IO.Path]::GetDirectoryName('%(_NetCordRuntimesDir.FullPath)')) diff --git a/NetCord.Natives/NetCord.Natives.targets b/NetCord.Natives/NetCord.Natives.targets index 93d835ee..0e8c8d0f 100644 --- a/NetCord.Natives/NetCord.Natives.targets +++ b/NetCord.Natives/NetCord.Natives.targets @@ -34,9 +34,9 @@ Condition="'$(PublishAot)' == 'true' and '%(_NetCordStaticLink.Identity)' != ''"/> <_RunExclude Include="$(NetCordNativesRuntimesDir)\**\native\%(_NetCordExcluded.Identity)*.*" - Condition="'@(_NetCordExcluded.Identity)' != ''" /> + Condition="'@(_NetCordExcluded.Identity)' != '' and '$(NetCordNativesRuntimesDir)' != ''" /> <_RunExclude Include="$(NetCordNativesRuntimesDir)\**\native\lib%(_NetCordExcluded.Identity)*.*" - Condition="'@(_NetCordExcluded.Identity)' != ''" /> + Condition="'@(_NetCordExcluded.Identity)' != '' and '$(NetCordNativesRuntimesDir)' != ''" /> diff --git a/NetCord.slnx b/NetCord.slnx index 2338dbc5..30dd9aff 100644 --- a/NetCord.slnx +++ b/NetCord.slnx @@ -84,8 +84,7 @@ - - + @@ -99,7 +98,6 @@ - diff --git a/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj index 2eade721..2fae99ab 100644 --- a/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj +++ b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/NativeAotApp.csproj @@ -9,6 +9,7 @@ true true true + true false ..\..\..\..\NetCord.Natives diff --git a/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs index 111e3a69..bf91f09c 100644 --- a/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs +++ b/Tests/NetCord.Natives.Tests/Assets/NativeAotApp/Program.cs @@ -25,66 +25,8 @@ 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()}"); + Console.WriteLine($"Sodium Init: {NativeProbes.SodiumInit()}. 0 = OK"); Console.WriteLine($"Opus Version String: {Marshal.PtrToStringAnsi(NativeProbes.OpusGetVersionString())}"); Console.WriteLine($"Zstd Version Number: {NativeProbes.ZstdVersionNumber()}"); - - var cwd = Path.GetFullPath(Directory.GetCurrentDirectory()); - Console.WriteLine($"Current working directory: {cwd}"); - - string[] libraries = new[] { "libdave", "libsodium", "opus", "zstd" }; - var process = Process.GetCurrentProcess(); - var modules = process.Modules; - - bool allUnderCwd = true; - - foreach (var lib in libraries) - { - ProcessModule? found = null; - foreach (ProcessModule m in modules) - { - if (!string.IsNullOrEmpty(m.FileName) && m.FileName.IndexOf(lib, StringComparison.OrdinalIgnoreCase) >= 0) - { - found = m; - break; - } - if (!string.IsNullOrEmpty(m.ModuleName) && m.ModuleName.IndexOf(lib, StringComparison.OrdinalIgnoreCase) >= 0) - { - found = m; - break; - } - } - - if (found == null) - { - Console.WriteLine($"Library '{lib}' not found among loaded modules."); - allUnderCwd = false; - continue; - } - - Console.WriteLine($"{lib} loaded from: {found.FileName}"); - if (!IsUnderDirectory(found.FileName, cwd)) - { - Console.WriteLine($"Library '{lib}' is not under current working directory."); - allUnderCwd = false; - } - } - - Environment.Exit(allUnderCwd ? 0 : 1); - } - - private static bool IsUnderDirectory(string path, string directory) - { - try - { - var fullPath = Path.GetFullPath(path); - var fullDir = Path.GetFullPath(directory).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; - var comparison = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - return fullPath.StartsWith(fullDir, comparison); - } - catch - { - return false; - } } } diff --git a/Tests/NetCord.Natives.Tests/NativesBuildTests.cs b/Tests/NetCord.Natives.Tests/NativesBuildTests.cs index b47e24bb..f0bc87c6 100644 --- a/Tests/NetCord.Natives.Tests/NativesBuildTests.cs +++ b/Tests/NetCord.Natives.Tests/NativesBuildTests.cs @@ -80,25 +80,42 @@ public void AllLibraryImportsExistInBinary(string libName, string className) [DataRow("libdave;libsodium;opus;zstd")] public void NativeAotStaticLinking(string libName) { + var projectDirectory = Path.Combine(AppContext.BaseDirectory, "Assets", "NativeAotApp"); + var projectFile = Path.Combine(projectDirectory, "NativeAotApp.csproj"); + var generatedProjectFile = Path.Combine(projectDirectory, "NativeAotApp.g.csproj"); + try { + 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.GetCustomAttribute()?.Value; + 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:NetCordDirectPInvoke=\"{libName}\""); + buildProcess.StartInfo.ArgumentList.Add($"-p:VcpkgRoot={vcpkgRoot}"); - TestContext.WriteLine($"Building Native AoT app: 'dotnet {buildProcess.StartInfo.ArgumentList.Aggregate((a, b) => $"{a} {b}")}'"); + TestContext.WriteLine($"Building Native AoT app in ({projectDirectory}): 'dotnet {buildProcess.StartInfo.ArgumentList.Aggregate((a, b) => $"{a} {b}")}'"); - buildProcess.StartInfo.WorkingDirectory = Path.Combine(AppContext.BaseDirectory, "Assets", "NativeAotApp"); + buildProcess.StartInfo.WorkingDirectory = projectDirectory; buildProcess.StartInfo.RedirectStandardOutput = true; buildProcess.StartInfo.RedirectStandardError = true; buildProcess.Start(); @@ -111,8 +128,10 @@ public void NativeAotStaticLinking(string libName) // 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(AppContext.BaseDirectory, "Assets", "NativeAotApp", - "bin", "release", RuntimeInformation.RuntimeIdentifier, "publish", "NativeAotApp.exe"); + 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}'"); diff --git a/Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj b/Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj index 9ddd5a5b..4eaa6e27 100644 --- a/Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj +++ b/Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj @@ -35,6 +35,14 @@ <_Parameter1>NetCordNativesDir <_Parameter2>$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\$(NetCordNativesDir)')) + + <_Parameter1>VcpkgRoot + <_Parameter2>$(VcpkgRoot) + + + <_Parameter1>TargetFramework + <_Parameter2>$(TargetFramework) + From ee00a64e9bd479b8c03b9a67358c635df61444be Mon Sep 17 00:00:00 2001 From: Haves Irfan <20160532+ha-ves@users.noreply.github.com> Date: Sat, 9 May 2026 01:30:34 +0900 Subject: [PATCH 09/10] refactor native build/test pipeline - add reusable vcpkg setup - pack per-RID native nupkgs - streamline NativeAOT test logging - tighten native linking targets --- .github/actions/setup-vcpkg/action.yml | 43 ++++ .github/workflows/build-and-publish.yml | 10 +- .github/workflows/build-natives.yml | 182 +++++--------- .github/workflows/build.yml | 18 +- NetCord.Natives.slnx | 20 ++ NetCord.Natives/NetCord.Natives.csproj | 3 +- NetCord.Natives/NetCord.Natives.targets | 35 ++- .../Assets/NativeAotApp/NativeAotApp.csproj | 6 + .../Assets/NativeAotApp/Program.cs | 35 +-- Tests/NetCord.Natives.Tests/NativeProbes.cs | 78 ++++++ .../NativesBuildTests.cs | 225 +++++++++--------- .../NetCord.Natives.Tests.csproj | 23 +- 12 files changed, 378 insertions(+), 300 deletions(-) create mode 100644 .github/actions/setup-vcpkg/action.yml create mode 100644 Tests/NetCord.Natives.Tests/NativeProbes.cs diff --git a/.github/actions/setup-vcpkg/action.yml b/.github/actions/setup-vcpkg/action.yml new file mode 100644 index 00000000..58416aea --- /dev/null +++ b/.github/actions/setup-vcpkg/action.yml @@ -0,0 +1,43 @@ +name: Setup vcpkg +description: Setup vcpkg with fallback to submodule checkout + +runs: + using: composite + steps: + - name: Resolve or checkout vcpkg + shell: pwsh + run: | + # Ensure vcpkg cache and downloads directories exist + New-Item -ItemType Directory -Path $env:VCPKG_BINARY_SOURCES.Split(',')[1] -Force | Out-Null + New-Item -ItemType Directory -Path $env:VCPKG_DOWNLOADS -Force | Out-Null + + $vcpkgRoot = $env:VCPKG_INSTALLATION_ROOT + if (-not $vcpkgRoot) { + $vcpkgCommand = Get-Command vcpkg -ErrorAction SilentlyContinue + if ($vcpkgCommand) { + $vcpkgRoot = Split-Path -Parent $vcpkgCommand.Source + } + } + + if (-not $vcpkgRoot -or -not (Test-Path $vcpkgRoot)) { + Write-Host "vcpkg not found in runner image, checking out from submodule..." + git config --file .gitmodules --get-regexp path | while-object { $_.Split()[-1] | where-object { $_ -match 'vcpkg' } } | ForEach-Object { + git submodule update --init --recursive $_ + } + $vcpkgRoot = Join-Path $env:GITHUB_WORKSPACE "vcpkg" + if (-not (Test-Path $vcpkgRoot)) { + Write-Error "vcpkg submodule not found and vcpkg executable not available" + exit 1 + } + } + + if (Test-Path $vcpkgRoot) { + Write-Host "Bootstrapping vcpkg from: $vcpkgRoot" + if ($IsWindows) { + & "$vcpkgRoot/bootstrap-vcpkg.bat" -disableMetrics + } else { + & "$vcpkgRoot/bootstrap-vcpkg.sh" -disableMetrics + } + } + + "VCPKG_ROOT=$vcpkgRoot" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index a27c0d24..c9de0054 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -18,8 +18,8 @@ jobs: - name: Download Build Artifacts uses: actions/download-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: - name: Build Artifacts - path: artifacts/build + name: NuGet Packages + path: artifacts/pkgs - name: Download Documentation Artifacts uses: actions/download-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a @@ -39,11 +39,7 @@ jobs: env: KEY: ${{ secrets.NUGET_API_KEY }} run: | - dotnet nuget push artifacts/build/NetCord/bin/Release/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate - dotnet nuget push artifacts/build/NetCord.Services/bin/Release/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate - dotnet nuget push artifacts/build/Hosting/NetCord.Hosting/bin/Release/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate - dotnet nuget push artifacts/build/Hosting/NetCord.Hosting.Services/bin/Release/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate - dotnet nuget push artifacts/build/Hosting/NetCord.Hosting.AspNetCore/bin/Release/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate + dotnet nuget push artifacts/pkgs/*.nupkg -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 c56bd99c..a5079949 100644 --- a/.github/workflows/build-natives.yml +++ b/.github/workflows/build-natives.yml @@ -1,7 +1,7 @@ name: Build, Test, and Package NetCord.Natives on: - workflow_dispatch: {} + workflow_dispatch: push: paths: - 'NetCord.Natives/**' @@ -17,6 +17,12 @@ jobs: CI: true VCPKG_BINARY_SOURCES: clear;files,${{ github.workspace }}/.vcpkg-cache,readwrite VCPKG_DOWNLOADS: ${{ github.workspace }}/.vcpkg-downloads + VcpkgOSTarget: ${{ matrix.vcpkg-os-target }} + VcpkgPlatformTarget: ${{ matrix.vcpkg-platform-target }} + Configuration: Release + RuntimeIdentifier: ${{ matrix.rid }} + DotnetVerbose: minimal + strategy: fail-fast: false matrix: @@ -26,15 +32,15 @@ jobs: vcpkg-os-target: windows vcpkg-platform-target: x64 - rid: win-arm64 - runs-on: windows-latest + runs-on: windows-11-arm vcpkg-os-target: windows vcpkg-platform-target: arm64 - rid: osx-x64 - runs-on: macos-latest + runs-on: macos-15-intel vcpkg-os-target: osx vcpkg-platform-target: x64 - rid: osx-arm64 - runs-on: macos-latest + runs-on: macos-15 vcpkg-os-target: osx vcpkg-platform-target: arm64 - rid: linux-x64 @@ -42,83 +48,62 @@ jobs: vcpkg-os-target: linux vcpkg-platform-target: x64 - rid: linux-arm64 - runs-on: ubuntu-latest + runs-on: ubuntu-24.04-arm vcpkg-os-target: linux vcpkg-platform-target: arm64 steps: - name: Checkout uses: actions/checkout@v6.0.2 + with: + fetch-depth: 0 - - name: Cache vcpkg installs - uses: actions/cache@v5.0.5 + - name: Restore vcpkg cache + uses: actions/cache/restore@v5.0.5 with: path: | .vcpkg-cache .vcpkg-downloads - key: vcpkg-${{ runner.os }}-${{ matrix.rid }}- - ${{ hashFiles( - 'NetCord.Natives/vcpkg.json', - 'NetCord.Natives/natives-ports/**' - ) }} + key: vcpkg-${{ runner.os }}-${{ matrix.rid }}-${{ hashFiles('NetCord.Natives/vcpkg.json', 'NetCord.Natives/natives-ports/**') }} restore-keys: | vcpkg-${{ runner.os }}-${{ matrix.rid }}- vcpkg-${{ runner.os }}- - - name: Resolve vcpkg from runner image - shell: pwsh - run: | - # Ensure vcpkg cache and downloads directories exist - New-Item -ItemType Directory -Path $env:VCPKG_BINARY_SOURCES.Split(',')[1] -Force | Out-Null - New-Item -ItemType Directory -Path $env:VCPKG_DOWNLOADS -Force | Out-Null - - $vcpkgRoot = $env:VCPKG_INSTALLATION_ROOT - if (-not $vcpkgRoot) { - $vcpkgCommand = Get-Command vcpkg -ErrorAction Stop - $vcpkgRoot = Split-Path -Parent $vcpkgCommand.Source - } + - name: Setup vcpkg + uses: ./.github/actions/setup-vcpkg - if (Test-Path $vcpkgRoot) { - # Bootstrap vcpkg - if ($IsWindows) { - & "$vcpkgRoot/bootstrap-vcpkg.bat" -disableMetrics - } else { - & "$vcpkgRoot/bootstrap-vcpkg.sh" -disableMetrics - } - } - - "VCPKG_ROOT=$vcpkgRoot" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + - name: Setup .NET Core SDK + uses: actions/setup-dotnet@v5.2.0 + with: + global-json-file: global.json - name: Install native build tools (macOS) if: runner.os == 'macOS' shell: bash run: | brew update - brew install autoconf automake libtool pkg-config - - - name: Install ARM64 cross-compiler (linux-arm64) - if: matrix.rid == 'linux-arm64' - shell: bash - run: | - sudo apt-get update - sudo apt-get install -yq gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev-arm64-cross + brew install autoconf automake libtool - name: Restore shell: pwsh run: | - dotnet restore NetCord.Natives/NetCord.Natives.csproj -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgOSTarget=${{ matrix.vcpkg-os-target }} -p:VcpkgPlatformTarget=${{ matrix.vcpkg-platform-target }} + dotnet restore Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj -v normal - - name: Build native outputs for ${{ matrix.rid }} + - name: Build natives projects for ${{ matrix.rid }} shell: pwsh - run: | - dotnet build NetCord.Natives/NetCord.Natives.csproj -c Release --no-restore -p:GeneratePackageOnBuild=false -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgOSTarget=${{ matrix.vcpkg-os-target }} -p:VcpkgPlatformTarget=${{ matrix.vcpkg-platform-target }} + 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 }} + -p:AppendNativeAotAppProps="RuntimeIdentifier=${{ env.RuntimeIdentifier }}" + -p:GeneratePackageOnBuild=false - - name: Test native outputs for ${{ matrix.rid }} + - name: Test natives outputs for ${{ matrix.rid }} shell: pwsh - run: | - dotnet test NetCord.Natives.Tests/NetCord.Natives.Tests.csproj -c Release -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgOSTarget=${{ matrix.vcpkg-os-target }} -p:VcpkgPlatformTarget=${{ matrix.vcpkg-platform-target }} --verbosity normal + run: > + dotnet test Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj --no-restore --no-build -v ${{ env.DotnetVerbose }} + --logger GitHubActions - name: List built for ${{ matrix.rid }} + if: ${{ always() }} shell: pwsh run: | $rid = '${{ matrix.rid }}' @@ -131,98 +116,49 @@ jobs: foreach ($folder in @('bin','lib')) { $path = "NetCord.Natives/bin/$rid/$variant/$triplet/$folder" if (Test-Path $path) { - Write-Host "Contents of: $path" - Get-ChildItem -Path $path -Recurse -Force | Select-Object FullName, Mode, Length, LastWriteTime + Get-ChildItem -Path $path -Recurse } else { - Write-Host "[Info]Not found: $path" + Write-Host "[Info] Not found: $path" } } } } - - name: Upload artifact - uses: actions/upload-artifact@v7.0.1 - with: - name: ${{ matrix.rid }} - path: NetCord.Natives/bin/${{ matrix.rid }} - - pack-natives: - needs: build-natives - runs-on: ubuntu-latest - env: - CI: true - VCPKG_BINARY_SOURCES: clear;files,${{ github.workspace }}/.vcpkg-cache,readwrite - VCPKG_DOWNLOADS: ${{ github.workspace }}/.vcpkg-downloads - VcpkgArtifactsRoot: ${{ github.workspace }}/artifacts - - steps: - - name: Checkout - uses: actions/checkout@v6.0.2 - - - name: Download native artifacts - uses: actions/download-artifact@v8.0.1 - with: - path: artifacts - merge-multiple: false - - - name: Resolve vcpkg from runner image - shell: pwsh - run: | - # Ensure vcpkg cache and downloads directories exist - New-Item -ItemType Directory -Path $env:VCPKG_BINARY_SOURCES.Split(',')[1] -Force | Out-Null - New-Item -ItemType Directory -Path $env:VCPKG_DOWNLOADS -Force | Out-Null - - $vcpkgRoot = $env:VCPKG_INSTALLATION_ROOT - if (-not $vcpkgRoot) { - $vcpkgCommand = Get-Command vcpkg -ErrorAction Stop - $vcpkgRoot = Split-Path -Parent $vcpkgCommand.Source - } - - if (Test-Path $vcpkgRoot) { - # Bootstrap vcpkg - if ($IsWindows) { - & "$vcpkgRoot/bootstrap-vcpkg.bat" -disableMetrics - } else { - & "$vcpkgRoot/bootstrap-vcpkg.sh" -disableMetrics - } - } - - "VCPKG_ROOT=$vcpkgRoot" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 - - - name: Restore (required for pack) + - name: Pack NuGet Package for ${{ matrix.rid }} shell: pwsh - run: | - dotnet restore NetCord.Natives/NetCord.Natives.csproj -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgEnabled=false + run: > + dotnet pack NetCord.Natives/NetCord.Natives.csproj --no-restore --no-build -v ${{ env.DotnetVerbose }} + -o NetCord.Natives/bin/${{ matrix.rid }} + -p:CI=false - - name: Build cross-platform .NET (required for pack) - shell: pwsh - run: | - dotnet build NetCord.Natives/NetCord.Natives.csproj -c Release --no-restore -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgEnabled=false - - - name: Pack combined nupkg - shell: pwsh - run: | - dotnet pack NetCord.Natives/NetCord.Natives.csproj -c Release --no-build --no-restore -p:VcpkgRoot=$env:VCPKG_ROOT -p:VcpkgArtifactsRoot=$env:VcpkgArtifactsRoot -o $env:VcpkgArtifactsRoot/package - - - name: Upload nupkg + - name: Upload NuGet Package Artifact uses: actions/upload-artifact@v7.0.1 with: - name: NetCord.Natives.Package - path: artifacts/package/*.nupkg + name: NetCord.Natives.${{ matrix.rid }}.nupkg + path: NetCord.Natives/bin/${{ matrix.rid }}/*.nupkg + + - name: Save vcpkg cache + if: ${{ always() }} + uses: actions/cache/save@v5.0.5 + with: + path: | + .vcpkg-cache + .vcpkg-downloads + key: vcpkg-${{ runner.os }}-${{ matrix.rid }}-${{ hashFiles('NetCord.Natives/vcpkg.json', 'NetCord.Natives/natives-ports/**') }} publish: - needs: pack-natives + needs: [build-test-natives] if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest steps: - - name: Download nupkg + - name: Download All NuGet Package Artifacts uses: actions/download-artifact@v8.0.1 with: - name: NetCord.Natives.Package - path: artifacts/package + path: artifacts/pkgs + pattern: NetCord.Natives*.nupkg - - name: Publish Package + - name: Publish packages env: KEY: ${{ secrets.NUGET_API_KEY }} run: | - dotnet nuget push artifacts/package/*.nupkg -k $KEY -s https://api.nuget.org/v3/index.json --skip-duplicate + dotnet nuget push artifacts/pkgs/*.nupkg -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 89f48496..d6c0ce12 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,7 +34,9 @@ jobs: - name: Restore dependencies shell: *dev-shell - run: dotnet restore + run: | + dotnet restore NetCord.slnx + dotnet restore NetCord.Natives.slnx -p:VcpkgRoot=$VCPKG_INSTALLATION_ROOT -p:VcpkgEnabled=false - name: Build shell: *dev-shell @@ -68,16 +70,16 @@ jobs: npm run test npm run build - - name: Upload Build Artifacts + - name: Upload NuGet Package Artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: Build Artifacts + name: NuGet Packages path: | - NetCord/bin/Release - NetCord.Services/bin/Release - Hosting/NetCord.Hosting/bin/Release - Hosting/NetCord.Hosting.Services/bin/Release - Hosting/NetCord.Hosting.AspNetCore/bin/Release + 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 - name: Upload Documentation Artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/NetCord.Natives.slnx b/NetCord.Natives.slnx index 1f71c569..8e306060 100644 --- a/NetCord.Natives.slnx +++ b/NetCord.Natives.slnx @@ -1,7 +1,27 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/NetCord.Natives/NetCord.Natives.csproj b/NetCord.Natives/NetCord.Natives.csproj index 735f1e98..3d38cf8c 100644 --- a/NetCord.Natives/NetCord.Natives.csproj +++ b/NetCord.Natives/NetCord.Natives.csproj @@ -157,7 +157,7 @@ + Returns="@(_CopyOutput)"> <_CopyOutput Include="%(NativesRuntime.FullPath)"> @@ -166,6 +166,7 @@ + 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
![Storage Requirement](https://img.shields.io/badge/Runtime-Package-blue)
| +|----------|-----|-------------------------|-----------| +| Windows x64 | `win-x64` | βœ“ (Static CRT /MT) | ![Windows x64](https://img.shields.io/badge/6_MB-42_MB_(176_MB)-blue) | +| Windows ARM64 | `win-arm64` | βœ“ (Static CRT /MT) | ![Windows ARM64](https://img.shields.io/badge/6_MB-41_MB_(183_MB)-blue) | +| Linux x64 | `linux-x64` | βœ“ (Dynamic CRT) | ![Linux x64](https://img.shields.io/badge/10_MB-9_MB_(28_MB)-blue) | +| Linux ARM64 | `linux-arm64` | βœ“ (Dynamic CRT) | ![Linux ARM64](https://img.shields.io/badge/10_MB-8_MB_(26_MB)-blue) | +| β“˜ macOS x64 | `osx-x64` | βœ“ (Dynamic CRT) | ![macOS x64](https://img.shields.io/badge/15_MB-11_MB_(28_MB)-blue) | +| β“˜ macOS ARM64 | `osx-arm64` | βœ“ (Dynamic CRT) | ![macOS ARM64](https://img.shields.io/badge/13_MB-10_MB_(25_MB)-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'" />
- @@ -131,29 +134,27 @@ - + @(StatusLines, ' ') - + <_VcpkgPkg Include="@(NativeLibLic)"> - - $([System.Text.RegularExpressions.Regex]::Match('$(StatusText)', 'Package: %(Identity)\n(?:.*\n)*?Version: (.*?)\n').Groups[1].Value) + + $([System.Text.RegularExpressions.Regex]::Match('$(StatusText)', 'Package: %(Identity)\n(?:.*\n)*?Version: (.*?)\n').Groups[1].Value) - - - - <_Parameter1>%(_VcpkgPkg.Identity) - <_Parameter2>%(_VcpkgPkg.Version) - - - + + + $(PackageDescription) (Includes: @(_VcpkgPkg->'%(Identity) v%(Version)', ', ')) + + - + GetMissingLibraryImports(string libName, s return missingExports; } + /* + * This method is not working on macOS due to System.Diagnostics.ProcessModule + * not listing native libraries loaded by the runtime's dynamic library loader (dyld). + * This is likely because dyld does not expose these libraries to the process in the + * same way that LoadLibrary on Windows or dlopen on Linux does, and as a result they + * do not appear in the list of modules for the process. This is a known limitation + * when trying to inspect loaded native libraries on macOS using .NET, and may require + * platform-specific workarounds or tools to verify the presence of native libraries + * on that platform. + */ internal static IntPtr GetLoadedNativeModuleHandle(string libName) { _ = libName switch @@ -60,19 +70,25 @@ internal static IntPtr GetLoadedNativeModuleHandle(string libName) _ => throw new InvalidOperationException($"Unknown library name '{libName}' provided to test."), }; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + throw new PlatformNotSupportedException("Retrieving loaded native module handles is not supported on macOS due to platform limitations."); + var module = System.Diagnostics.Process.GetCurrentProcess().Modules .Cast() .FirstOrDefault(m => { - var moduleName = Path.GetFileName(m.ModuleName); + var moduleName = Path.GetFileName(m.FileName); 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)) + { + Console.WriteLine($"Successfully obtained a handle for native module '{module.ModuleName}' using path '{module.FileName}'."); return libHandleByPath; + } - throw new InvalidOperationException($"Failed to obtain a handle for native module '{libName}'."); + throw new InvalidOperationException($"Failed to obtain a handle for native module '{module.ModuleName}'."); } } diff --git a/Tests/NetCord.Natives.Tests/NativesBuildTests.cs b/Tests/NetCord.Natives.Tests/NativesBuildTests.cs index f2462585..8de10c24 100644 --- a/Tests/NetCord.Natives.Tests/NativesBuildTests.cs +++ b/Tests/NetCord.Natives.Tests/NativesBuildTests.cs @@ -25,6 +25,7 @@ public void NativesLoaded(string libName) } [TestMethod] + [OSCondition(ConditionMode.Exclude, OperatingSystems.OSX)] [DataRow("libdave", "NetCord.Gateway.Voice.Dave")] [DataRow("libsodium", "NetCord.Gateway.Voice.Encryption.XChaCha20Poly1305")] [DataRow("opus", "NetCord.Gateway.Voice.Opus")] @@ -93,6 +94,8 @@ public void NativeAotStaticLinking(string libName) getRunCmd.StartInfo.ArgumentList.Add("-t:GetTargetPath"); getRunCmd.StartInfo.ArgumentList.Add("-getProperty:PublishDir"); getRunCmd.StartInfo.ArgumentList.Add("--no-restore"); + + getRunCmd.StartInfo.ArgumentList.Add("--bl"); string? runCmdOutput = null; diff --git a/Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj b/Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj index 6dd95eed..636b6f9a 100644 --- a/Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj +++ b/Tests/NetCord.Natives.Tests/NetCord.Natives.Tests.csproj @@ -27,7 +27,6 @@ -