diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 51fb96f3c..abf9f29d3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -107,12 +107,24 @@ jobs:
name: Linux-sdk
path: package-dev/Plugins/Linux/Sentry~
+ - name: Download LinuxArm64 SDK
+ uses: ./.github/actions/wait-for-artifact
+ with:
+ name: LinuxArm64-sdk
+ path: package-dev/Plugins/Linux/Sentry~
+
- name: Download LinuxNative SDK
uses: ./.github/actions/wait-for-artifact
with:
name: LinuxNative-sdk
path: package-dev/Plugins/Linux/SentryNative~
+ - name: Download LinuxNativeArm64 SDK
+ uses: ./.github/actions/wait-for-artifact
+ with:
+ name: LinuxNativeArm64-sdk
+ path: package-dev/Plugins/Linux/SentryNative~
+
- name: Download Windows SDK
uses: ./.github/actions/wait-for-artifact
with:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6151395e8..0c89a1986 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -39,10 +39,17 @@ jobs:
include:
- target: Android
host: ubuntu-latest
+ # 22.04 keeps the required glibc version low.
- target: Linux
- host: ubuntu-latest
+ host: ubuntu-22.04
- target: LinuxNative
- host: ubuntu-latest
+ host: ubuntu-22.04
+ - target: Linux
+ arch: Arm64
+ host: ubuntu-22.04-arm
+ - target: LinuxNative
+ arch: Arm64
+ host: ubuntu-22.04-arm
- target: Windows
host: windows-latest
- target: WindowsNative
@@ -54,6 +61,7 @@ jobs:
uses: ./.github/workflows/sdk.yml
with:
target: ${{ matrix.target }}
+ arch: ${{ matrix.arch }}
runsOn: ${{ matrix.host }}
build-unity-sdk:
@@ -448,6 +456,21 @@ jobs:
with:
unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version[0] }}
+ test-build-linux-arm64:
+ # Unity ships an arm64 Linux player only as an IL2CPP dedicated server, from 6000.3 on. One version,
+ # Native backend only: the x86_64 matrix already covers the version spread and Breakpad.
+ # The version is the newest matrix entry, so Unity-bump PRs for older versions skip it.
+ name: Build Linux Server arm64 ${{ needs.create-unity-matrix.outputs.unity-latest }} Integration Test
+ if: ${{ !startsWith(github.ref, 'refs/heads/release/') && fromJSON(needs.create-unity-matrix.outputs.unity-latest) >= 6000.3 }}
+ needs: [test-create, create-unity-matrix]
+ secrets:
+ UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }}
+ SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
+ SENTRY_TEST_DSN: ${{ secrets.SENTRY_TEST_DSN }}
+ uses: ./.github/workflows/test-build-linux-arm64.yml
+ with:
+ unity-version: ${{ needs.create-unity-matrix.outputs.unity-latest }}
+
test-build-windows-mono:
# The Mono scripting backend resolves P/Invokes differently than IL2CPP, so it needs its own
# player. One version, default (Crashpad) backend only: the IL2CPP matrix already covers the
@@ -517,6 +540,20 @@ jobs:
platform: windows
backend: ${{ matrix.backend }}
+ test-run-linux-arm64:
+ name: Run Linux arm64 native ${{ needs.create-unity-matrix.outputs.unity-latest }} Integration Test
+ if: ${{ !startsWith(github.ref, 'refs/heads/release/') && fromJSON(needs.create-unity-matrix.outputs.unity-latest) >= 6000.3 }}
+ needs: [test-build-linux-arm64, create-unity-matrix]
+ secrets:
+ SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
+ SENTRY_TEST_DSN: ${{ secrets.SENTRY_TEST_DSN }}
+ uses: ./.github/workflows/test-run-desktop.yml
+ with:
+ unity-version: ${{ needs.create-unity-matrix.outputs.unity-latest }}
+ platform: linux
+ architecture: arm64
+ backend: native
+
test-run-windows-x86:
name: Run Windows x86 native ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version[0] }} Integration Test
if: ${{ !startsWith(github.ref, 'refs/heads/release/') }}
diff --git a/.github/workflows/create-unity-matrix.yml b/.github/workflows/create-unity-matrix.yml
index 2ad5eb93d..d437a84e6 100644
--- a/.github/workflows/create-unity-matrix.yml
+++ b/.github/workflows/create-unity-matrix.yml
@@ -16,6 +16,9 @@ on:
unity-matrix:
description: 'Unity version matrix'
value: ${{ jobs.create-unity-matrix.outputs.unity-matrix }}
+ unity-latest:
+ description: 'Newest Unity version in the matrix'
+ value: ${{ jobs.create-unity-matrix.outputs.unity-latest }}
env:
# Unity versions used in PRs
@@ -28,6 +31,7 @@ jobs:
runs-on: ubuntu-latest
outputs:
unity-matrix: ${{ steps.set-matrix.outputs.matrix }}
+ unity-latest: ${{ steps.set-matrix.outputs.latest }}
steps:
- name: Set Unity version matrices based on event type
id: set-matrix
@@ -49,4 +53,5 @@ jobs:
fi
# Unity version matrix
- echo "matrix={\"unity-version\":$versions}" >> $GITHUB_OUTPUT
\ No newline at end of file
+ echo "matrix={\"unity-version\":$versions}" >> $GITHUB_OUTPUT
+ echo "latest=$(jq -r '.[-1]' <<< "$versions")" >> $GITHUB_OUTPUT
\ No newline at end of file
diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml
index d565d5539..a7660b02f 100644
--- a/.github/workflows/sdk.yml
+++ b/.github/workflows/sdk.yml
@@ -8,11 +8,16 @@ on:
target:
required: true
type: string
+ arch:
+ required: false
+ type: string
+ default: ""
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_NOLOGO: 1
TARGET: ${{ inputs.target }}
+ ARTIFACT: ${{ inputs.target }}${{ inputs.arch }}
jobs:
build:
@@ -63,7 +68,7 @@ jobs:
# hash of package/package.json for cache busting on release builds (version bump)
path: |
package-dev/Plugins
- key: sdk=${{ env.TARGET }}-${{ hashFiles('submodules-status', 'package/package.json', 'Directory.Build.targets', 'build/native-sdks.targets', 'sdk-static/**', 'scripts/build-cocoa-sdk.ps1', 'scripts/build-native-ndk-local.ps1') }}
+ key: sdk=${{ env.ARTIFACT }}-${{ hashFiles('submodules-status', 'package/package.json', 'Directory.Build.targets', 'build/native-sdks.targets', 'sdk-static/**', 'scripts/build-cocoa-sdk.ps1', 'scripts/build-native-ndk-local.ps1') }}
- name: Installing Linux Dependencies
if: ${{ (env.TARGET == 'Linux' || env.TARGET == 'LinuxNative') && steps.cache.outputs.cache-hit != 'true' }}
@@ -143,7 +148,7 @@ jobs:
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ env.TARGET == 'Linux' }}
with:
- name: ${{ env.TARGET }}-sdk
+ name: ${{ env.ARTIFACT }}-sdk
path: package-dev/Plugins/Linux/Sentry~
# Lower retention period - we only need this to retry CI.
retention-days: 14
@@ -151,7 +156,7 @@ jobs:
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ env.TARGET == 'LinuxNative' }}
with:
- name: ${{ env.TARGET }}-sdk
+ name: ${{ env.ARTIFACT }}-sdk
path: package-dev/Plugins/Linux/SentryNative~
# Lower retention period - we only need this to retry CI.
retention-days: 14
diff --git a/.github/workflows/test-build-linux-arm64.yml b/.github/workflows/test-build-linux-arm64.yml
new file mode 100644
index 000000000..aba9ae72c
--- /dev/null
+++ b/.github/workflows/test-build-linux-arm64.yml
@@ -0,0 +1,133 @@
+name: "Test: Build Linux Server arm64"
+on:
+ workflow_call:
+ inputs:
+ unity-version:
+ required: true
+ type: string
+ secrets:
+ UNITY_LICENSE_SERVER_CONFIG:
+ required: true
+ SENTRY_AUTH_TOKEN:
+ required: true
+ SENTRY_TEST_DSN:
+ required: true
+
+defaults:
+ run:
+ shell: pwsh
+
+jobs:
+ build:
+ name: Linux Server arm64 ${{ inputs.unity-version }}
+ # Unity has no arm64 Linux editor and the GameCI images lack the arm64 server player, so this
+ # cross-compiles on x86_64 with the Unity CLI install instead. Pinned to 24.04: Unity's bundled
+ # IL2CPP linker needs libxml2.so.2, which 26.04 no longer ships.
+ runs-on: ubuntu-24.04
+ env:
+ UNITY_VERSION: ${{ inputs.unity-version }}
+ BUILD_PLATFORM: Linux-Server-arm64
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+
+ - name: Setup Unity
+ run: ./scripts/setup-unity.ps1 -UnityVersion "${{ inputs.unity-version }}" -Modules linux-server
+ env:
+ UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }}
+
+ - name: Download IntegrationTest project
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: test-${{ env.UNITY_VERSION }}
+
+ - name: Extract project archive
+ run: tar -xvzf test-project.tar.gz
+
+ - name: Restore Unity Library cache
+ uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
+ with:
+ path: samples/IntegrationTest/Library
+ key: it-library-linux-arm64-${{ env.UNITY_VERSION }}-${{ github.run_id }}
+ restore-keys: |
+ it-library-linux-arm64-${{ env.UNITY_VERSION }}-
+
+ - name: Download UPM package
+ uses: ./.github/actions/wait-for-artifact
+ with:
+ name: package-release
+
+ - name: Extract UPM package
+ run: ./test/Scripts.Integration.Test/extract-package.ps1
+
+ - name: Add Sentry to the project
+ run: ./test/Scripts.Integration.Test/add-sentry.ps1 -UnityPath "$env:UNITY_PATH" -PackagePath "test-package-release"
+
+ - name: Download DependencyConflict package
+ uses: ./.github/actions/wait-for-artifact
+ with:
+ name: dependency-conflict-package
+ path: dependency-conflict-package
+
+ - name: Add DependencyConflict to the project
+ run: ./test/Scripts.Integration.Test/add-dependency-conflict.ps1 -PackagePath "dependency-conflict-package"
+
+ - name: Configure Sentry
+ run: ./test/Scripts.Integration.Test/configure-sentry.ps1 -UnityPath "$env:UNITY_PATH" -Platform Linux
+ env:
+ SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }}
+
+ - name: Use the Native backend
+ run: |
+ $optionsPath = "samples/IntegrationTest/Assets/Scripts/IntegrationOptionsConfiguration.cs"
+ $content = Get-Content $optionsPath -Raw
+ $content = $content -replace 'LinuxBackend = LinuxBackend.Breakpad', 'LinuxBackend = LinuxBackend.Native'
+ Set-Content $optionsPath $content
+
+ - name: Build with Sentry SDK
+ run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Linux-Server-arm64 -UnityVersion "$env:UNITY_VERSION"
+ env:
+ SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
+
+ - name: Assert the player is arm64
+ run: |
+ $info = file samples/IntegrationTest/Build/test
+ Write-Host $info
+ if ($info -notmatch 'aarch64') { throw "Expected an arm64 player" }
+
+ - name: Assert symbols and sources were uploaded
+ run: ./test/Scripts.Integration.Test/assert-symbol-upload.ps1 -LogPath unity.log
+
+ # We create tar explicitly because upload-artifact is slow for many files.
+ - name: Create archive
+ run: |
+ Remove-Item -Recurse -Force samples/IntegrationTest/Build/*_BackUpThisFolder_ButDontShipItWithYourGame -ErrorAction SilentlyContinue
+ Copy-Item unity.log samples/IntegrationTest/Build/ -ErrorAction SilentlyContinue
+ tar -cvzf test-app-desktop.tar.gz samples/IntegrationTest/Build
+
+ - name: Upload test app
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: testapp-desktop-compiled-${{ env.UNITY_VERSION }}-linux-arm64-native
+ if-no-files-found: error
+ path: test-app-desktop.tar.gz
+ retention-days: 14
+
+ - name: Save Unity Library cache
+ if: github.ref == 'refs/heads/main'
+ uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
+ with:
+ path: samples/IntegrationTest/Library
+ key: it-library-linux-arm64-${{ env.UNITY_VERSION }}-${{ github.run_id }}
+
+ - name: Upload IntegrationTest project on failure
+ if: ${{ failure() }}
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: failed-project-desktop-linux-arm64-${{ env.UNITY_VERSION }}
+ path: |
+ samples/IntegrationTest
+ unity.log
+ !samples/IntegrationTest/Build/*_BackUpThisFolder_ButDontShipItWithYourGame
+ retention-days: 14
diff --git a/.github/workflows/test-run-desktop.yml b/.github/workflows/test-run-desktop.yml
index 4cf4f7e24..7c13cf380 100644
--- a/.github/workflows/test-run-desktop.yml
+++ b/.github/workflows/test-run-desktop.yml
@@ -18,7 +18,7 @@ on:
required: false
type: string
default: ""
- description: "Only set for non-default architectures, e.g. x86 for the 32-bit Windows player."
+ description: "Only set for non-default architectures, e.g. x86 for the 32-bit Windows player or arm64 for the Linux server."
scripting:
required: false
type: string
@@ -37,7 +37,7 @@ defaults:
jobs:
run:
name: ${{ inputs.platform }}${{ inputs.architecture && format(' {0}', inputs.architecture) || '' }}${{ inputs.scripting && format(' {0}', inputs.scripting) || '' }}${{ inputs.backend && format(' ({0})', inputs.backend) || '' }} ${{ inputs.unity-version }}
- runs-on: ${{ inputs.platform == 'linux' && 'ubuntu-latest' || inputs.platform == 'macos' && 'macos-latest' || 'windows-latest' }}
+ runs-on: ${{ inputs.platform == 'linux' && (inputs.architecture == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest') || inputs.platform == 'macos' && 'macos-latest' || 'windows-latest' }}
env:
SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
@@ -71,8 +71,9 @@ jobs:
- name: Run Integration Tests (Linux)
if: inputs.platform == 'linux'
timeout-minutes: 20
+ # The arm64 player is a headless dedicated server and needs no display.
run: |
- xvfb-run pwsh -Command '
+ ${{ inputs.architecture != 'arm64' && 'xvfb-run' || '' }} pwsh -Command '
$env:SENTRY_TEST_PLATFORM = "Desktop";
$env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/test";
Invoke-Pester -Path test/IntegrationTest/Integration.Tests.ps1 -CI'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f0906ca56..cc014a7b1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,8 +8,13 @@
### Fixes
+- The Linux native SDK now loads on distros with older glibc versions, such as Ubuntu 22.04 and Debian 12. ([#2894](https://github.com/getsentry/sentry-unity/pull/2894))
- Attaching a screenshot no longer stalls the main thread. Unless `SetBeforeSendScreenshot` is set, the frame gets read back from the GPU asynchronously and encoded on a background thread. ([#2835](https://github.com/getsentry/sentry-unity/pull/2835))
+### Features
+
+- The SDK now supports Linux ARM64 dedicated servers (Unity 6000.3 and newer, IL2CPP). The native SDK for both backends ships for `x86_64` and `ARM64`, and the build picks the one matching the player. ([#2894](https://github.com/getsentry/sentry-unity/pull/2894))
+
### Dependencies
- Bump CLI from v3.8.0 to v3.8.2 ([#2893](https://github.com/getsentry/sentry-unity/pull/2893))
diff --git a/build/native-sdks.targets b/build/native-sdks.targets
index c5008ba7b..f15748bd4 100644
--- a/build/native-sdks.targets
+++ b/build/native-sdks.targets
@@ -14,8 +14,11 @@
$(RepoRoot)modules/sentry-native/
- $(SentryArtifactsDestination)Linux/Sentry~/
- $(SentryArtifactsDestination)Linux/SentryNative~/
+
+ ARM64
+ x86_64
+ $(SentryArtifactsDestination)Linux/Sentry~/$(SentryLinuxArch)/
+ $(SentryArtifactsDestination)Linux/SentryNative~/$(SentryLinuxArch)/
$(SentryArtifactsDestination)Windows/Sentry~/
$(SentryArtifactsDestination)Windows/SentryNative~/
@@ -46,19 +49,17 @@
-
+
-
-
-
-
+
@@ -70,7 +71,7 @@
-
+
-
+
-
-
-
-
-
-
+
@@ -110,7 +106,7 @@
-
+
diff --git a/docs/agent-guides/platform-native.md b/docs/agent-guides/platform-native.md
index ceb8da4e7..1767d1e76 100644
--- a/docs/agent-guides/platform-native.md
+++ b/docs/agent-guides/platform-native.md
@@ -27,7 +27,7 @@ Experimental native modes raise minimum shutdown timeout to 10 seconds.
- Windows: runtime files beside player `.exe`; the library lands as `sentry-native.dll`. Both
architectures ship, under `x86/` and `x64/` inside each backend folder, because Unity still
builds a 32-bit standalone player. `BuildTarget.StandaloneWindows` picks `x86/`.
-- Linux: `libsentry-native.so` under `_Data/Plugins/x86_64`; native daemon beside executable.
+- Linux: `libsentry-native.so` under `_Data/Plugins/` (`x86_64` or `ARM64`, from the Linux build settings' architecture); native daemon beside executable.
- macOS: `libsentry-native.dylib` in `.app/Contents/PlugIns`; handler in `.app/Contents/MacOS`. Cocoa's `Sentry.dylib` keeps its name, it is dlopened not P/Invoked.
- Post-build copies names through unchanged; stale cleanup wipes pre-rename names.
- Placement is what sentry-native probes by default. `SentryUnityOptions.NativeHandlerPath` overrides it via `handler_path`, for handlers staged somewhere else; sidecars the handler resolves relative to itself, such as `crashpad_wer.dll`, move with it.
diff --git a/scripts/download-native-sdks.ps1 b/scripts/download-native-sdks.ps1
index efccd359d..65b35f864 100644
--- a/scripts/download-native-sdks.ps1
+++ b/scripts/download-native-sdks.ps1
@@ -32,12 +32,22 @@ $SDKs = @(
@{
Name = "Linux"
Destination = Join-Path $ArtifactsDestination "Linux/Sentry~"
- CheckFile = "libsentry-native.so"
+ CheckFile = "x86_64/libsentry-native.so"
+ },
+ @{
+ Name = "LinuxArm64"
+ Destination = Join-Path $ArtifactsDestination "Linux/Sentry~"
+ CheckFile = "ARM64/libsentry-native.so"
},
@{
Name = "LinuxNative"
Destination = Join-Path $ArtifactsDestination "Linux/SentryNative~"
- CheckFile = "libsentry-native.so"
+ CheckFile = "x86_64/libsentry-native.so"
+ },
+ @{
+ Name = "LinuxNativeArm64"
+ Destination = Join-Path $ArtifactsDestination "Linux/SentryNative~"
+ CheckFile = "ARM64/libsentry-native.so"
},
@{
Name = "Android"
@@ -201,11 +211,11 @@ if ($currentBranch -and $currentBranch -ne 'main' -and $currentBranch -ne 'HEAD'
$failed = @()
foreach ($sdk in $sdksToDownload) {
Write-Host "Downloading $($sdk.Name) SDK..." -ForegroundColor Yellow
- if (Try-DownloadSDK -Name $sdk.Name -Destination $sdk.Destination -RunId $mainRunId) {
+ if ((Try-DownloadSDK -Name $sdk.Name -Destination $sdk.Destination -RunId $mainRunId) -and (Test-SDKPresent $sdk)) {
Write-Host " Downloaded from main run $mainRunId" -ForegroundColor Green
continue
}
- if ($branchRunId -and (Try-DownloadSDK -Name $sdk.Name -Destination $sdk.Destination -RunId $branchRunId)) {
+ if ($branchRunId -and (Try-DownloadSDK -Name $sdk.Name -Destination $sdk.Destination -RunId $branchRunId) -and (Test-SDKPresent $sdk)) {
Write-Host " Not on main; downloaded from branch '$currentBranch' run $branchRunId" -ForegroundColor Yellow
continue
}
diff --git a/scripts/setup-unity.ps1 b/scripts/setup-unity.ps1
index 4a875311a..dc1b3b237 100644
--- a/scripts/setup-unity.ps1
+++ b/scripts/setup-unity.ps1
@@ -9,6 +9,7 @@ $PSNativeCommandUseErrorActionPreference = $true
$CliVersion = '1.0.0-beta.14'
$CliBinaries = @{
'darwin-arm64' = @('unity-darwin-arm64', '367ea124c61e91dc22409699cb40d2e65d23952f3220e911ccea1f6d8a1ff37f')
+ 'linux-x64' = @('unity-linux-x64', 'd9a2c1716ce723b970898f7f40adc09cceee04e84ce2f1f1638f07ac3e9898aa')
'win32-x64' = @('unity-windows-x64.exe', '78761a0547cd4a9bc212e27a217c66e6a621bf48c753ba440e4b2327ee7ff8ac')
}
@@ -20,7 +21,7 @@ $env:UNITY_NON_INTERACTIVE = '1'
$env:UNITY_NO_BANNER = '1'
$env:UNITY_NO_PAGER = '1'
-# Unity CLI, pinned by version and checksum. Linux CI uses the GameCI Docker images instead (scripts/ci-docker.sh).
+# Unity CLI, pinned by version and checksum.
$os = $IsWindows ? 'win32' : $IsMacOS ? 'darwin' : 'linux'
$arch = "$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)".ToLower()
$cliFile, $cliSha = $CliBinaries["$os-$arch"]
@@ -51,7 +52,7 @@ if ($env:UNITY_LICENSE_SERVER_CONFIG) {
New-Item -ItemType Directory -Force -Path 'C:/ProgramData/Unity/config' | Out-Null
Set-Content -Path 'C:/ProgramData/Unity/config/services-config.json' -Value $env:UNITY_LICENSE_SERVER_CONFIG
} else {
- $unityDataDir = '/Library/Application Support/Unity'
+ $unityDataDir = $IsMacOS ? '/Library/Application Support/Unity' : '/usr/share/unity3d'
$configFile = Join-Path $cliDir 'services-config.json'
Set-Content -Path $configFile -Value $env:UNITY_LICENSE_SERVER_CONFIG
sudo mkdir -p "$unityDataDir/config"
diff --git a/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs b/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs
index b98df038e..5b728242a 100644
--- a/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs
+++ b/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs
@@ -10,6 +10,7 @@
using UnityEditor.Build.Reporting;
using System.Diagnostics;
using System.Linq;
+using System.Reflection;
namespace Sentry.Unity.Editor.Native;
@@ -84,7 +85,9 @@ _ when targetGroup.IsSwitch2() => Path.GetDirectoryName(executablePath),
_ => string.Empty
};
- UploadDebugSymbols(logger, target, buildOutputDir, executableName, options, cliOptions, isMono);
+ var linuxArch = target == BuildTarget.StandaloneLinux64 ? GetLinuxArchitecture() : string.Empty;
+
+ UploadDebugSymbols(logger, target, buildOutputDir, executableName, linuxArch, options, cliOptions, isMono);
if (!IsEnabledForPlatform(target, options))
{
@@ -108,10 +111,10 @@ _ when targetGroup.IsSwitch2() => Path.GetDirectoryName(executablePath),
else if (target == BuildTarget.StandaloneLinux64)
{
// Since the backend can change between iterative builds we need to clean up after ourselves
- CleanupStaleLinuxArtifacts(logger, buildOutputDir);
+ CleanupStaleLinuxArtifacts(logger, buildOutputDir, linuxArch);
}
- foreach (var artifact in GetNativePluginArtifact(target, options, executablePath, buildOutputDir))
+ foreach (var artifact in GetNativePluginArtifact(target, options, executablePath, buildOutputDir, linuxArch))
{
_ = Directory.CreateDirectory(Path.GetDirectoryName(artifact.Destination));
logger.LogDebug("Copying '{0}' to '{1}'", artifact.Source, artifact.Destination);
@@ -157,6 +160,24 @@ internal static string GetWindowsBackendDir(BuildTarget target, SentryUnityOptio
return Path.GetFullPath($"Packages/{SentryPackageInfo.GetName()}/Plugins/Windows/{backend}/{arch}");
}
+ // Linux ships x86_64 and ARM64, each in a subfolder named like the player's plugin folder. Both build for `StandaloneLinux64`,
+ // so the architecture comes from the Linux build settings.
+ internal static string GetLinuxBackendDir(SentryUnityOptions options, string arch)
+ {
+ var backend = options.Experimental.LinuxBackend == LinuxBackend.Native ? "SentryNative~" : "Sentry~";
+ return Path.GetFullPath($"Packages/{SentryPackageInfo.GetName()}/Plugins/Linux/{backend}/{arch}");
+ }
+
+ // Unity's Linux build picks the player from `UnityEditor.LinuxStandalone.UserBuildSettings.architecture`.
+ // It lives in the Linux module's assembly and is missing on versions without ARM64 support.
+ internal static string GetLinuxArchitecture()
+ {
+ var architecture = Type.GetType("UnityEditor.LinuxStandalone.UserBuildSettings, UnityEditor.LinuxStandalone.Extensions")
+ ?.GetProperty("architecture", BindingFlags.Public | BindingFlags.Static)
+ ?.GetValue(null);
+ return architecture?.ToString() == "ARM64" ? "ARM64" : "x86_64";
+ }
+
private readonly struct NativePluginArtifact(string source, string destination, bool isExecutable = false)
{
public readonly string Source = source;
@@ -165,7 +186,7 @@ private readonly struct NativePluginArtifact(string source, string destination,
}
private static IEnumerable GetNativePluginArtifact(
- BuildTarget target, SentryUnityOptions options, string executablePath, string buildOutputDir)
+ BuildTarget target, SentryUnityOptions options, string executablePath, string buildOutputDir, string linuxArch)
{
var pluginsPath = Path.GetFullPath($"Packages/{SentryPackageInfo.GetName()}/Plugins");
@@ -214,9 +235,7 @@ private static IEnumerable GetNativePluginArtifact(
break;
case BuildTarget.StandaloneLinux64:
- var linuxBackendSourcePath = options.Experimental.LinuxBackend == LinuxBackend.Native
- ? Path.Combine(pluginsPath, "Linux", "SentryNative~")
- : Path.Combine(pluginsPath, "Linux", "Sentry~");
+ var linuxBackendSourcePath = GetLinuxBackendDir(options, linuxArch);
if (!Directory.Exists(linuxBackendSourcePath))
{
var buildTarget = options.Experimental.LinuxBackend == LinuxBackend.Native ? "BuildLinuxNativeSDK" : "BuildLinuxSDK";
@@ -226,7 +245,7 @@ private static IEnumerable GetNativePluginArtifact(
}
// The Linux player resolves the P/Invoke from the plugin dir, and sentry-native spawns
// the crash daemon from next to the executable. Debug sidecars stay in the package.
- var linuxPluginDir = GetLinuxPluginDir(buildOutputDir);
+ var linuxPluginDir = GetLinuxPluginDir(buildOutputDir, linuxArch);
foreach (var file in Directory.GetFiles(linuxBackendSourcePath))
{
var name = Path.GetFileName(file);
@@ -309,9 +328,9 @@ private static void CleanupStaleWindowsArtifacts(IDiagnosticLogger logger, strin
}
}
- // Unity places Linux native plugins under _Data/Plugins/x86_64, which the player adds
- // to its dlopen search path. We resolve the data dir by globbing (the player name isn't known here).
- private static string GetLinuxPluginDir(string buildOutputDir)
+ // Unity places Linux native plugins under _Data/Plugins/, using the plugin importer's
+ // CPU names (`x86_64`, `ARM64`). We resolve the data dir by globbing (the player name isn't known here).
+ private static string GetLinuxPluginDir(string buildOutputDir, string arch)
{
var dataDir = Directory.GetDirectories(buildOutputDir, "*_Data").FirstOrDefault();
if (dataDir is null)
@@ -320,20 +339,20 @@ private static string GetLinuxPluginDir(string buildOutputDir)
$"Could not locate the player '*_Data' directory under '{buildOutputDir}' to place the Sentry native plugin.");
}
- return Path.Combine(dataDir, "Plugins", "x86_64");
+ return Path.Combine(dataDir, "Plugins", arch);
}
// Switching Linux backends between iterative builds leaves the other backend's artifacts behind
// (a stale sentry-crash next to the player, or the other backend's libsentry.so in the plugin dir).
// Wipe them before copying the current backend's files in.
- private static void CleanupStaleLinuxArtifacts(IDiagnosticLogger logger, string buildOutputDir)
+ private static void CleanupStaleLinuxArtifacts(IDiagnosticLogger logger, string buildOutputDir, string arch)
{
var stalePaths = new List { Path.Combine(buildOutputDir, "sentry-crash") };
var dataDir = Directory.GetDirectories(buildOutputDir, "*_Data").FirstOrDefault();
if (dataDir is not null)
{
- stalePaths.Add(Path.Combine(dataDir, "Plugins", "x86_64", "libsentry.so"));
- stalePaths.Add(Path.Combine(dataDir, "Plugins", "x86_64", LinuxLibraryName));
+ stalePaths.Add(Path.Combine(dataDir, "Plugins", arch, "libsentry.so"));
+ stalePaths.Add(Path.Combine(dataDir, "Plugins", arch, LinuxLibraryName));
}
foreach (var stale in stalePaths)
@@ -364,7 +383,7 @@ internal static void AddPath(List paths, string path, IDiagnosticLogger
}
private static void UploadDebugSymbols(IDiagnosticLogger logger, BuildTarget target, string buildOutputDir,
- string executableName, SentryUnityOptions options, SentryCliOptions? cliOptions, bool isMono)
+ string executableName, string linuxArch, SentryUnityOptions options, SentryCliOptions? cliOptions, bool isMono)
{
var projectDir = Directory.GetParent(Application.dataPath)?.FullName ?? "";
if (cliOptions?.IsValid(logger, EditorUserBuildSettings.development) is not true)
@@ -454,11 +473,7 @@ private static void UploadDebugSymbols(IDiagnosticLogger logger, BuildTarget tar
// Sentry native SDK symbols from package.
// Glob the debug sidecars (libsentry.dbg.so, and sentry-crash.dbg for the native backend)
// from whichever backend's source dir is in use.
- var linuxBackendDir = options.Experimental.LinuxBackend == LinuxBackend.Native
- ? "SentryNative~"
- : "Sentry~";
- var linuxSymbolDir = Path.GetFullPath(
- $"Packages/{SentryPackageInfo.GetName()}/Plugins/Linux/{linuxBackendDir}");
+ var linuxSymbolDir = GetLinuxBackendDir(options, linuxArch);
if (Directory.Exists(linuxSymbolDir))
{
foreach (var file in Directory.GetFiles(linuxSymbolDir))
diff --git a/src/Sentry.Unity.Native/SentryNativeBridge.cs b/src/Sentry.Unity.Native/SentryNativeBridge.cs
index df451069e..095edad80 100644
--- a/src/Sentry.Unity.Native/SentryNativeBridge.cs
+++ b/src/Sentry.Unity.Native/SentryNativeBridge.cs
@@ -18,27 +18,29 @@ internal static class SentryNativeBridge
private static IDiagnosticLogger? Logger; // This is also the logger we're forwarding native messages to.
private static bool UseLibC;
+ private static bool IsLinuxArm64;
private static bool IsWindows;
public static bool Init(SentryUnityOptions options)
{
Logger = options.DiagnosticLogger;
- // Only x86-64 System V platforms need the `VaListLinux64` copy in `nativeLogImpl`. There
- // `va_list` is `__va_list_tag[1]` - an array that decays to a pointer - so `vsnprintf`
- // consumes the caller's list and it cannot be handed over twice. On AArch64 (Android, iOS,
- // Switch, Switch 2, Apple silicon) `va_list` is a struct that is copied by value at every
- // call, so the pointer can be reused as-is.
- UseLibC = Application.platform
- is RuntimePlatform.LinuxPlayer or RuntimePlatform.LinuxServer
- or RuntimePlatform.PS5;
+ // `nativeLogImpl` hands the `va_list` to `vsnprintf` twice. On x86-64 System V it is
+ // `__va_list_tag[1]` and on Linux AArch64 a 32-byte struct passed by reference, so `vsnprintf`
+ // may consume the caller's list and each call needs a fresh copy. On Windows and Apple
+ // silicon it is a plain pointer, and Switch copies it through `vsnprintf_sentry`.
+ UseLibC = Application.platform is RuntimePlatform.PS5;
if ((Application.platform
- is RuntimePlatform.OSXPlayer or RuntimePlatform.OSXServer)
+ is RuntimePlatform.LinuxPlayer or RuntimePlatform.LinuxServer
+ or RuntimePlatform.OSXPlayer or RuntimePlatform.OSXServer)
&& RuntimeInformation.ProcessArchitecture == Architecture.X64)
{
UseLibC = true;
}
+ IsLinuxArm64 = (Application.platform is RuntimePlatform.LinuxPlayer or RuntimePlatform.LinuxServer)
+ && RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
+
IsWindows = Application.platform
is RuntimePlatform.WindowsPlayer or RuntimePlatform.WindowsServer
or RuntimePlatform.GameCoreXboxSeries or RuntimePlatform.GameCoreXboxOne;
@@ -279,20 +281,11 @@ private static void nativeLogImpl(int cLevel, IntPtr format, IntPtr args, IntPtr
// For Linux and PlayStation, we must make a copy of the VaList to be able to pass it back...
if (UseLibC)
{
- var argsStruct = Marshal.PtrToStructure(args);
- var formattedLength = 0;
-
- WithMarshalledStruct(argsStruct, argsPtr =>
- {
- formattedLength = 1 + vsnprintf(IntPtr.Zero, UIntPtr.Zero, format, argsPtr);
- });
-
- WithAllocatedPtr(formattedLength, buffer =>
- WithMarshalledStruct(argsStruct, argsPtr =>
- {
- vsnprintf(buffer, (UIntPtr)formattedLength, format, argsPtr);
- message = Marshal.PtrToStringAnsi(buffer);
- }));
+ message = FormatWithCopiedVaList(format, args);
+ }
+ else if (IsLinuxArm64)
+ {
+ message = FormatWithCopiedVaList(format, args);
}
else
{
@@ -354,6 +347,38 @@ private struct VaListLinux64
IntPtr reg_save_area;
}
+ // AAPCS64 `va_list`
+ [StructLayout(LayoutKind.Sequential)]
+ private struct VaListAarch64
+ {
+ IntPtr stack;
+ IntPtr gr_top;
+ IntPtr vr_top;
+ int gr_offs;
+ int vr_offs;
+ }
+
+ private static string? FormatWithCopiedVaList(IntPtr format, IntPtr args) where T : struct
+ {
+ var argsStruct = Marshal.PtrToStructure(args);
+ var formattedLength = 0;
+ string? message = null;
+
+ WithMarshalledStruct(argsStruct, argsPtr =>
+ {
+ formattedLength = 1 + vsnprintf(IntPtr.Zero, UIntPtr.Zero, format, argsPtr);
+ });
+
+ WithAllocatedPtr(formattedLength, buffer =>
+ WithMarshalledStruct(argsStruct, argsPtr =>
+ {
+ vsnprintf(buffer, (UIntPtr)formattedLength, format, argsPtr);
+ message = Marshal.PtrToStringAnsi(buffer);
+ }));
+
+ return message;
+ }
+
private static void WithAllocatedPtr(int size, Action action)
{
var ptr = IntPtr.Zero;
diff --git a/test/Scripts.Integration.Test/Editor/Builder.cs b/test/Scripts.Integration.Test/Editor/Builder.cs
index 410709fbc..f4d960614 100644
--- a/test/Scripts.Integration.Test/Editor/Builder.cs
+++ b/test/Scripts.Integration.Test/Editor/Builder.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Reflection;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
@@ -9,7 +10,8 @@
public class Builder
{
public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group, BuildOptions buildOptions,
- string defaultBuildPath = "./Builds/", ScriptingImplementation scripting = ScriptingImplementation.IL2CPP)
+ string defaultBuildPath = "./Builds/", ScriptingImplementation scripting = ScriptingImplementation.IL2CPP,
+ StandaloneBuildSubtarget subtarget = StandaloneBuildSubtarget.Player)
{
Debug.Log("Builder: Starting to build");
@@ -21,9 +23,12 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group,
// Make sure the configuration is right.
EditorUserBuildSettings.selectedBuildTargetGroup = group;
+ var namedTarget = subtarget == StandaloneBuildSubtarget.Server
+ ? NamedBuildTarget.Server
+ : NamedBuildTarget.FromBuildTargetGroup(group);
EditorUserBuildSettings.development = false;
EditorUserBuildSettings.allowDebugging = false;
- PlayerSettings.SetScriptingBackend(NamedBuildTarget.FromBuildTargetGroup(group), scripting);
+ PlayerSettings.SetScriptingBackend(namedTarget, scripting);
// Making sure that the app keeps on running in the background. Linux CI is very unhappy with coroutines otherwise.
PlayerSettings.runInBackground = true;
@@ -34,7 +39,7 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group,
{
Debug.Log("Builder: Setting IL2CPP generation to OptimizeSpeed");
#if UNITY_2022_1_OR_NEWER
- PlayerSettings.SetIl2CppCodeGeneration(NamedBuildTarget.FromBuildTargetGroup(group), UnityEditor.Build.Il2CppCodeGeneration.OptimizeSpeed);
+ PlayerSettings.SetIl2CppCodeGeneration(namedTarget, UnityEditor.Build.Il2CppCodeGeneration.OptimizeSpeed);
#elif UNITY_2021_2_OR_NEWER
EditorUserBuildSettings.il2CppCodeGeneration = UnityEditor.Build.Il2CppCodeGeneration.OptimizeSpeed;
#endif
@@ -42,9 +47,9 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group,
Debug.Log("Builder: Configuring code stripping level");
#if UNITY_6000_0_OR_NEWER
- PlayerSettings.SetManagedStrippingLevel(NamedBuildTarget.FromBuildTargetGroup(group), ManagedStrippingLevel.High);
+ PlayerSettings.SetManagedStrippingLevel(namedTarget, ManagedStrippingLevel.High);
#else
- PlayerSettings.SetManagedStrippingLevel(NamedBuildTarget.FromBuildTargetGroup(group), ManagedStrippingLevel.Low);
+ PlayerSettings.SetManagedStrippingLevel(namedTarget, ManagedStrippingLevel.Low);
#endif
Debug.Log("Builder: Updating BuildPlayerOptions");
@@ -56,11 +61,16 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group,
options = buildOptions
};
+ if (subtarget == StandaloneBuildSubtarget.Server)
+ {
+ buildPlayerOptions.subtarget = (int)subtarget;
+ }
+
Debug.Log("Builder: Disabling optimizations to reduce build time");
// TODO Linux fails with `free(): invalid pointer` in the test, after everything seems to have shut down.
if (scripting == ScriptingImplementation.IL2CPP && target != BuildTarget.StandaloneLinux64)
{
- PlayerSettings.SetIl2CppCompilerConfiguration(NamedBuildTarget.FromBuildTargetGroup(group), Il2CppCompilerConfiguration.Debug);
+ PlayerSettings.SetIl2CppCompilerConfiguration(namedTarget, Il2CppCompilerConfiguration.Debug);
}
if (target == BuildTarget.Android)
@@ -171,6 +181,30 @@ public static void BuildLinuxIl2CPPPlayer()
defaultBuildPath: "./Builds/Linux/test");
}
+#if UNITY_6000_3_OR_NEWER
+ // Unity only ships an arm64 Linux player as a dedicated server (6000.3+, IL2CPP). The architecture
+ // setting lives in the Linux module's extension assembly, which only exists with that module installed.
+ [MenuItem("Tools/Builder/Linux Server arm64")]
+ public static void BuildLinuxServerArm64Il2CPPPlayer()
+ {
+ Debug.Log("Builder: Building Linux Server arm64 IL2CPP Player");
+ // `UserBuildSettings.architecture` writes to the settings of the subtarget selected in the build window.
+ typeof(EditorUserBuildSettings).GetProperty("selectedStandaloneBuildSubtarget", BindingFlags.Static | BindingFlags.NonPublic)
+ ?.SetValue(null, StandaloneBuildSubtarget.Server);
+
+ var architecture = Type.GetType("UnityEditor.LinuxStandalone.UserBuildSettings, UnityEditor.LinuxStandalone.Extensions")
+ ?.GetProperty("architecture") ?? throw new Exception("Linux build support is not installed.");
+ architecture.SetValue(null, OSArchitecture.ARM64);
+ if (!Equals(architecture.GetValue(null), OSArchitecture.ARM64))
+ {
+ throw new Exception($"Failed to set the Linux architecture to ARM64, got '{architecture.GetValue(null)}'.");
+ }
+
+ BuildIl2CPPPlayer(BuildTarget.StandaloneLinux64, BuildTargetGroup.Standalone, BuildOptions.StrictMode,
+ defaultBuildPath: "./Builds/Linux/test", subtarget: StandaloneBuildSubtarget.Server);
+ }
+#endif
+
[MenuItem("Tools/Builder/Android")]
public static void BuildAndroidIl2CPPPlayer()
{
diff --git a/test/Scripts.Integration.Test/globals.ps1 b/test/Scripts.Integration.Test/globals.ps1
index 275438f5d..d75340b4e 100644
--- a/test/Scripts.Integration.Test/globals.ps1
+++ b/test/Scripts.Integration.Test/globals.ps1
@@ -151,6 +151,7 @@ function BuildMethodFor([string] $platform)
"Windows-x86" { return "Builder.BuildWindowsX86Il2CPPPlayer" }
"Windows-Mono" { return "Builder.BuildWindowsMonoPlayer" }
"Linux" { return "Builder.BuildLinuxIl2CPPPlayer" }
+ "Linux-Server-arm64" { return "Builder.BuildLinuxServerArm64Il2CPPPlayer" }
"WebGL" { return "Builder.BuildWebGLPlayer" }
"iOS" { return "Builder.BuildIOSProject" }
"Switch" { return "Builder.BuildSwitchIL2CPPPlayer" }
diff --git a/test/Scripts.Tests/package-release.zip.snapshot b/test/Scripts.Tests/package-release.zip.snapshot
index 5d884886c..c07306eea 100644
--- a/test/Scripts.Tests/package-release.zip.snapshot
+++ b/test/Scripts.Tests/package-release.zip.snapshot
@@ -267,12 +267,20 @@ Plugins/iOS/SentryObjC.xcframework~/ios-arm64/dSYMs/SentryObjC.framework.dSYM/Co
Plugins/iOS/SentryObjC.xcframework~/ios-arm64/dSYMs/SentryObjC.framework.dSYM/Contents/Resources/Relocations/
Plugins/iOS/SentryObjC.xcframework~/ios-arm64/dSYMs/SentryObjC.framework.dSYM/Contents/Resources/Relocations/aarch64/SentryObjC.yml
Plugins/iOS/SentryObjC.xcframework~/ios-arm64/dSYMs/SentryObjC.framework.dSYM/Contents/Resources/DWARF/SentryObjC
-Plugins/Linux/SentryNative~/libsentry-native.so
-Plugins/Linux/SentryNative~/libsentry.dbg.so
-Plugins/Linux/SentryNative~/sentry-crash
-Plugins/Linux/SentryNative~/sentry-crash.dbg
-Plugins/Linux/Sentry~/libsentry-native.so
-Plugins/Linux/Sentry~/libsentry.dbg.so
+Plugins/Linux/Sentry~/
+Plugins/Linux/SentryNative~/
+Plugins/Linux/SentryNative~/ARM64/libsentry-native.so
+Plugins/Linux/SentryNative~/ARM64/libsentry.dbg.so
+Plugins/Linux/SentryNative~/ARM64/sentry-crash
+Plugins/Linux/SentryNative~/ARM64/sentry-crash.dbg
+Plugins/Linux/SentryNative~/x86_64/libsentry-native.so
+Plugins/Linux/SentryNative~/x86_64/libsentry.dbg.so
+Plugins/Linux/SentryNative~/x86_64/sentry-crash
+Plugins/Linux/SentryNative~/x86_64/sentry-crash.dbg
+Plugins/Linux/Sentry~/ARM64/libsentry-native.so
+Plugins/Linux/Sentry~/ARM64/libsentry.dbg.so
+Plugins/Linux/Sentry~/x86_64/libsentry-native.so
+Plugins/Linux/Sentry~/x86_64/libsentry.dbg.so
Plugins/Android/proguard-sentry-unity.pro
Plugins/Android/proguard-sentry-unity.pro.meta
Plugins/Android/Sentry~/epitaph.jar
diff --git a/test/Sentry.Unity.Editor.Tests/Native/BuildPostProcessTests.cs b/test/Sentry.Unity.Editor.Tests/Native/BuildPostProcessTests.cs
index c8817cfbc..1097a4037 100644
--- a/test/Sentry.Unity.Editor.Tests/Native/BuildPostProcessTests.cs
+++ b/test/Sentry.Unity.Editor.Tests/Native/BuildPostProcessTests.cs
@@ -97,4 +97,20 @@ public void GetWindowsBackendDir_ResolvesBackendAndArchitecture(
Assert.AreEqual(expectedArchDir, Path.GetFileName(actual));
Assert.AreEqual(expectedBackendDir, Path.GetFileName(Path.GetDirectoryName(actual)));
}
+
+ [TestCase(LinuxBackend.Breakpad, "Sentry~", "x86_64")]
+ [TestCase(LinuxBackend.Breakpad, "Sentry~", "ARM64")]
+ [TestCase(LinuxBackend.Native, "SentryNative~", "x86_64")]
+ [TestCase(LinuxBackend.Native, "SentryNative~", "ARM64")]
+ public void GetLinuxBackendDir_ResolvesBackendAndArchitecture(
+ LinuxBackend backend, string expectedBackendDir, string arch)
+ {
+ var options = new SentryUnityOptions();
+ options.Experimental.LinuxBackend = backend;
+
+ var actual = BuildPostProcess.GetLinuxBackendDir(options, arch);
+
+ Assert.AreEqual(arch, Path.GetFileName(actual));
+ Assert.AreEqual(expectedBackendDir, Path.GetFileName(Path.GetDirectoryName(actual)));
+ }
}