diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 94ca1885c..6abddf2c2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -88,6 +88,10 @@ jobs:
with:
name: package-release
+ - name: Check plugin platform scopes
+ shell: pwsh
+ run: ./test/Scripts.Tests/test-plugin-platforms.ps1
+
- name: Check snapshot
id: snapshot-check
shell: pwsh
@@ -427,6 +431,23 @@ jobs:
with:
unity-version: ${{ matrix.unity-version }}
+ 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
+ # version spread and both backends. The version is the first matrix entry rather than a
+ # literal, because `test-create` only builds what the matrix holds and Unity-bump PRs narrow
+ # it to the bumped version. The lists in create-unity-matrix.yml are oldest-first.
+ name: Build Windows Mono ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version[0] }} Integration Test
+ if: ${{ !startsWith(github.ref, 'refs/heads/release/') }}
+ 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-windows-mono.yml
+ with:
+ unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version[0] }}
+
test-build-macos:
name: Build macOS ${{ matrix.unity-version }} Integration Test
if: ${{ !startsWith(github.ref, 'refs/heads/release/') }}
@@ -479,6 +500,20 @@ jobs:
platform: windows
backend: ${{ matrix.backend }}
+ test-run-windows-mono:
+ name: Run Windows Mono ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version[0] }} Integration Test
+ if: ${{ !startsWith(github.ref, 'refs/heads/release/') }}
+ needs: [test-build-windows-mono, 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: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version[0] }}
+ platform: windows
+ scripting: mono
+ backend: crashpad
+
test-run-macos:
name: Run macOS ${{ matrix.backend }} ${{ matrix.unity-version }} Integration Test
if: ${{ !startsWith(github.ref, 'refs/heads/release/') }}
diff --git a/.github/workflows/test-build-windows-mono.yml b/.github/workflows/test-build-windows-mono.yml
new file mode 100644
index 000000000..66b7e29ee
--- /dev/null
+++ b/.github/workflows/test-build-windows-mono.yml
@@ -0,0 +1,137 @@
+name: "Test: Build Windows Mono"
+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: Windows Mono ${{ inputs.unity-version }}
+ runs-on: windows-latest
+ env:
+ UNITY_VERSION: ${{ inputs.unity-version }}
+ BUILD_PLATFORM: Windows-Mono
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+
+ - name: Load env
+ id: env
+ run: |
+ $u = (Get-Content scripts/unity-versions.json -Raw | ConvertFrom-Json).'${{ env.UNITY_VERSION }}'
+ "unityVersion=$($u.version)" >> $env:GITHUB_OUTPUT
+ "unityChangeset=$($u.changeset)" >> $env:GITHUB_OUTPUT
+
+ - name: Setup Unity
+ uses: getsentry/setup-unity@61c0c0944851685b6c1225e940d1b1ab349e3aa3
+ with:
+ unity-version: ${{ steps.env.outputs.unityVersion }}
+ unity-version-changeset: ${{ steps.env.outputs.unityChangeset }}
+ unity-modules: windows-il2cpp
+
+ - name: Create Unity license config
+ run: |
+ New-Item -Path "C:/ProgramData/Unity/config/" -ItemType Directory -Force
+ Set-Content -Path "C:/ProgramData/Unity/config/services-config.json" -Value "$env:UNITY_LICENSE_SERVER_CONFIG"
+ 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-windows-mono-${{ env.UNITY_VERSION }}-${{ github.run_id }}
+ restore-keys: |
+ it-library-windows-mono-${{ 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 Windows
+ env:
+ SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }}
+
+ # Crashpad backend only. The point of this job is that a Mono player resolves the
+ # P/Invokes to `sentry-native` instead of the managed `Sentry.dll` at all; both Windows
+ # backends bind against the same library, and the IL2CPP matrix already covers the
+ # backend spread. Crashpad is what players ship with by default.
+ - name: Build with Sentry SDK
+ run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Windows-Mono -UnityVersion "$env:UNITY_VERSION"
+ env:
+ SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
+
+ - 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 }}-windows-mono-crashpad
+ 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-windows-mono-${{ 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-windows-mono-${{ 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 979ee9a45..381d52da3 100644
--- a/.github/workflows/test-run-desktop.yml
+++ b/.github/workflows/test-run-desktop.yml
@@ -14,6 +14,11 @@ on:
type: string
default: ""
description: "macOS: native or cocoa. Windows: native or crashpad. Linux: native or breakpad."
+ scripting:
+ required: false
+ type: string
+ default: ""
+ description: "Only set for non-default scripting backends, e.g. mono for the Windows Mono player."
secrets:
SENTRY_AUTH_TOKEN:
required: true
@@ -26,7 +31,7 @@ defaults:
jobs:
run:
- name: ${{ inputs.platform }}${{ inputs.backend && format(' ({0})', inputs.backend) || '' }} ${{ inputs.unity-version }}
+ name: ${{ inputs.platform }}${{ 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' }}
env:
SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }}
@@ -43,7 +48,7 @@ jobs:
- name: Download test app artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: testapp-desktop-compiled-${{ inputs.unity-version }}-${{ inputs.platform }}${{ inputs.backend && format('-{0}', inputs.backend) || '' }}
+ name: testapp-desktop-compiled-${{ inputs.unity-version }}-${{ inputs.platform }}${{ inputs.scripting && format('-{0}', inputs.scripting) || '' }}${{ inputs.backend && format('-{0}', inputs.backend) || '' }}
- name: Extract test app
run: tar -xvzf test-app-desktop.tar.gz
@@ -81,6 +86,8 @@ jobs:
- name: Run Integration Tests (Windows)
if: inputs.platform == 'windows'
timeout-minutes: 20
+ env:
+ SENTRY_TEST_SCRIPTING_BACKEND: ${{ inputs.scripting }}
run: |
$env:SENTRY_TEST_PLATFORM = "Desktop"
$env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/test.exe"
@@ -90,7 +97,7 @@ jobs:
if: ${{ failure() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
- name: testapp-desktop-logs-${{ inputs.platform }}${{ inputs.backend && format('-{0}', inputs.backend) || '' }}-${{ inputs.unity-version }}
+ name: testapp-desktop-logs-${{ inputs.platform }}${{ inputs.scripting && format('-{0}', inputs.scripting) || '' }}${{ inputs.backend && format('-{0}', inputs.backend) || '' }}-${{ inputs.unity-version }}
path: |
test/IntegrationTest/results/
retention-days: 14
diff --git a/.gitignore b/.gitignore
index 8ba14ec97..89dcca713 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,6 +63,10 @@ package-dev/Plugins/Windows/SentryNative~/*
package-dev/Plugins/Linux/Sentry~/*
package-dev/Plugins/Linux/SentryNative~/*
+# macOS SDK files
+package-dev/Plugins/macOS/Sentry~/*
+package-dev/Plugins/macOS/SentryNative~/*
+
# CLI
package-dev/Editor/sentry-cli
diff --git a/AGENTS.md b/AGENTS.md
index 8a9726921..37984a84d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -50,4 +50,4 @@ Read only guide relevant to task. Do not import all guides at startup.
## Commits
- Use direct, capitalized commit subjects without conventional-commit prefixes.
-- Include the committing agent's own `Co-Authored-By` attribution when a commit is requested.
+- Do not add agent attribution trailers such as `Co-Authored-By`.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5606b8ef0..01aa1d6d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,10 @@
### Fixes
+- When targeting Windows using the `Mono` scripting backend the SDK now correctly loads `sentry-native` to capture native crashes. ([#2842](https://github.com/getsentry/sentry-unity/pull/2842))
- Fixed a `NoSuchFieldError` during initialization on Android when setting the `sample rate`. ([#2838](https://github.com/getsentry/sentry-unity/issues/2838))
+- Individual assemblies and plugins are now scoped to the platforms that use them. ([#2848](https://github.com/getsentry/sentry-unity/pull/2848))
+- When targeting Nintendo Switch or Switch 2 the SDK no longer fails to initialize the native layer at runtime. The respective stubs are now written to `Assets/Plugins/Sentry` so the SDK can enable and disable them before the build starts. ([#2849](https://github.com/getsentry/sentry-unity/pull/2849))
### Dependencies
diff --git a/build/native-sdks.targets b/build/native-sdks.targets
index 1b6f0cc53..ca6b954aa 100644
--- a/build/native-sdks.targets
+++ b/build/native-sdks.targets
@@ -18,6 +18,12 @@
$(SentryArtifactsDestination)Linux/SentryNative~/
$(SentryArtifactsDestination)Windows/Sentry~/
$(SentryArtifactsDestination)Windows/SentryNative~/
+
+
+ sentry-native.dll
+ libsentry-native.so
+ libsentry-native.dylib
+
@@ -66,17 +73,17 @@
-
+
-
+
-
+
@@ -89,6 +96,7 @@
+
@@ -100,16 +108,16 @@
-
+
-
+
-
+
@@ -130,6 +138,7 @@
+
@@ -142,19 +151,19 @@
-
+
-
+
@@ -166,6 +175,7 @@
+
@@ -180,7 +190,6 @@
-
@@ -189,8 +198,9 @@
+
-
+
+
+
@@ -220,19 +232,19 @@
-
+
-
+
-
+
-
+
+
+
+ $(PackageRuntimePath)/Sentry.Unity.Native.Android.dll
+
diff --git a/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs b/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs
index 3a3629fe2..fd943e7c8 100644
--- a/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs
+++ b/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs
@@ -139,6 +139,11 @@ _ when target.IsSwitch2() => options.SwitchNativeSupportEnabled,
_ => false,
};
+ // Only needed to clear stale artifacts; the package already ships these names.
+ internal const string WindowsLibraryName = "sentry-native.dll";
+ internal const string LinuxLibraryName = "libsentry-native.so";
+ internal const string MacOSLibraryName = "libsentry-native.dylib";
+
private readonly struct NativePluginArtifact(string source, string destination, bool isExecutable = false)
{
public readonly string Source = source;
@@ -165,9 +170,8 @@ private static IEnumerable GetNativePluginArtifact(
$"Sentry Windows plugin directory not found: {windowsBackendSourcePath}\n" +
$"Run 'dotnet msbuild /t:{buildTarget} src/Sentry.Unity' (or 'dotnet msbuild /t:DownloadNativeSDKs src/Sentry.Unity') to populate it.");
}
- // Flat copy of every non-PDB file next to the player .exe — sentry.dll and the
- // crash handler (crashpad_handler.exe / sentry-crash.exe) all sit at the build root.
- // PDBs stay in the package and are consumed at symbol-upload time only.
+ // Windows resolves both the library and the crash handler next to the player .exe.
+ // PDBs stay in the package for symbol upload.
foreach (var file in Directory.GetFiles(windowsBackendSourcePath))
{
if (file.EndsWith(".pdb", StringComparison.OrdinalIgnoreCase))
@@ -189,8 +193,7 @@ private static IEnumerable GetNativePluginArtifact(
{
var name = Path.GetFileName(file);
var isDylib = name.EndsWith(".dylib", StringComparison.OrdinalIgnoreCase);
- // The .dylibs need to go into the `*.app/Contents/Plugins` dirctory and will be picked
- // up by unity. The crash handler (sentry-native) needs to be next to the game's executable
+ // Unity loads dylibs from PlugIns; the crash handler has to be next to the executable.
var desination = Path.Combine(contents, isDylib ? "PlugIns" : "MacOS", name);
yield return new NativePluginArtifact(
file,
@@ -210,10 +213,8 @@ private static IEnumerable GetNativePluginArtifact(
$"Sentry Linux plugin directory not found: {linuxBackendSourcePath}\n" +
$"Run 'dotnet msbuild /t:{buildTarget} src/Sentry.Unity' (or 'dotnet msbuild /t:DownloadNativeSDKs src/Sentry.Unity') to populate it.");
}
- // libsentry.so must sit in the player's native plugin dir (_Data/Plugins/x86_64) where the
- // Linux player resolves DllImport("sentry"). The crash daemon (sentry-crash, native backend only)
- // sits next to the player executable so sentry-native can spawn it on crash.
- // The .dbg.so / .dbg debug sidecars stay in the package and are consumed at symbol-upload time only.
+ // 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);
foreach (var file in Directory.GetFiles(linuxBackendSourcePath))
{
@@ -252,11 +253,8 @@ private static IEnumerable GetNativePluginArtifact(
}
}
- // On case-insensitive APFS, leftover artifacts from a prior build with
- // the *other* macOS backend break DllImport("sentry") resolution
- // (Sentry.dylib gets picked over libsentry.dylib, surfacing as
- // `sentry_options_new` not found at runtime). Wipe both candidates
- // before copying the current backend's files in.
+ // A prior build with the other backend, or an older SDK's pre-rename library, would otherwise
+ // leave a second library behind in PlugIns.
private static void CleanupStaleMacOSArtifacts(IDiagnosticLogger logger, string executablePath)
{
var contents = Path.Combine(executablePath, "Contents");
@@ -264,6 +262,7 @@ private static void CleanupStaleMacOSArtifacts(IDiagnosticLogger logger, string
{
Path.Combine(contents, "PlugIns", "Sentry.dylib"),
Path.Combine(contents, "PlugIns", "libsentry.dylib"),
+ Path.Combine(contents, "PlugIns", MacOSLibraryName),
Path.Combine(contents, "MacOS", "sentry-crash"),
})
{
@@ -287,6 +286,8 @@ private static void CleanupStaleWindowsArtifacts(IDiagnosticLogger logger, strin
Path.Combine(buildOutputDir, "crashpad_wer.dll"),
Path.Combine(buildOutputDir, "sentry-crash.exe"),
Path.Combine(buildOutputDir, "sentry-wer.dll"),
+ Path.Combine(buildOutputDir, "sentry.dll"),
+ Path.Combine(buildOutputDir, WindowsLibraryName),
})
{
if (File.Exists(stale))
@@ -321,6 +322,7 @@ private static void CleanupStaleLinuxArtifacts(IDiagnosticLogger logger, string
if (dataDir is not null)
{
stalePaths.Add(Path.Combine(dataDir, "Plugins", "x86_64", "libsentry.so"));
+ stalePaths.Add(Path.Combine(dataDir, "Plugins", "x86_64", LinuxLibraryName));
}
foreach (var stale in stalePaths)
@@ -494,7 +496,7 @@ private static void UploadDebugSymbols(IDiagnosticLogger logger, BuildTarget tar
if (options.Experimental.MacosBackend == MacosBackend.Native)
{
var packageMacOSDir = $"Packages/{SentryPackageInfo.GetName()}/Plugins/macOS/SentryNative~";
- AddPath(paths, Path.GetFullPath($"{packageMacOSDir}/libsentry.dylib.dSYM"), logger);
+ AddPath(paths, Path.GetFullPath($"{packageMacOSDir}/libsentry-native.dylib.dSYM"), logger);
AddPath(paths, Path.GetFullPath($"{packageMacOSDir}/sentry-crash.dSYM"), logger);
}
else
diff --git a/src/Sentry.Unity.Editor/Native/SwitchNativePluginBuildPreProcess.cs b/src/Sentry.Unity.Editor/Native/SwitchNativePluginBuildPreProcess.cs
index ed03280e0..8edbc8b9f 100644
--- a/src/Sentry.Unity.Editor/Native/SwitchNativePluginBuildPreProcess.cs
+++ b/src/Sentry.Unity.Editor/Native/SwitchNativePluginBuildPreProcess.cs
@@ -1,5 +1,3 @@
-using System.IO;
-using System.Linq;
using Sentry.Extensibility;
using UnityEditor;
using UnityEditor.Build;
@@ -8,32 +6,15 @@
namespace Sentry.Unity.Editor.Native;
///
-/// Manages native plugin stubs for Nintendo Switch builds.
+/// Defensively fails a Switch build if the no-op stubs are not set up.
///
///
-/// For Nintendo Switch, users must compile and provide their own static native Sentry library.
-/// This preprocessor detects whether the user has provided the required native files and:
-///
-/// - If all required files are present: disables the stub (real library will be linked)
-/// - If files are missing: enables the stub (provides no-op implementations to satisfy linker)
-/// - If files are partially present: warns the user about misconfiguration
-///
+/// should have set this up already. This is the guard if it does not.
+/// We cannot repair the setup here. Unity collects native plugins as the build starts, so writing
+/// an asset from a build callback may or may not reach the player.
///
internal class SwitchNativePluginBuildPreProcess : IPreprocessBuildWithReport
{
- ///
- /// Both platforms share one stub, so the required libraries are what differ between them. The build target's
- /// name doubles as the directory name, i.e. `Switch` and `Switch2`.
- ///
- internal static string[] RequiredFilesFor(BuildTarget target)
- {
- return
- [
- $"Assets/Plugins/Sentry/{target}/libsentry.a",
- $"Assets/Plugins/Sentry/{target}/libzstd.a"
- ];
- }
-
public int callbackOrder => -100;
public void OnPreprocessBuild(BuildReport report)
@@ -45,69 +26,24 @@ public void OnPreprocessBuild(BuildReport report)
var options = SentryScriptableObject.LoadOptions(isBuilding: true);
var logger = options?.DiagnosticLogger ?? new UnityLogger(new SentryUnityOptions());
+ var target = report.summary.platform;
- ConfigureStub(logger, options?.SwitchNativeSupportEnabled ?? false, report.summary.platform);
- }
-
- internal static void ConfigureStub(IDiagnosticLogger logger, bool nativeSupportEnabled, BuildTarget target)
- {
- var requiredFiles = RequiredFilesFor(target);
-
- logger.LogDebug("{0} native support: checking for required files:\n{1}",
- target, string.Join("\n", requiredFiles.Select(f => $" - {f}")));
-
- // One stub serves both platforms; the importer tracks compatibility per build target, so
- // enabling it for one does not affect the other.
- var stubPath = Path.Combine("Packages", SentryPackageInfo.GetName(), "Plugins", "Switch", "sentry_native_stubs.c");
-
- var importer = AssetImporter.GetAtPath(stubPath) as PluginImporter;
- if (importer == null)
+ if (SwitchNativeStub.IsInSync(target))
{
- logger.LogError("Failed to get PluginImporter for stub at '{0}'. Skipping stub configuration.", stubPath);
return;
}
- var existingFiles = requiredFiles.Where(File.Exists).ToList();
- var missingFiles = requiredFiles.Except(existingFiles).ToList();
+ SwitchNativeStub.Sync(logger, target);
- var someFilesPresent = existingFiles.Count > 0 && missingFiles.Count > 0;
- if (someFilesPresent)
- {
- logger.LogWarning(
- "{0} native support is partially configured. Missing files:\n{1}\n" +
- "Please add all required files to enable native support, or remove all files to fall back on no-op stubs.\n" +
- "Build sentry-switch and copy the libraries to the expected locations. " +
- "See: https://github.com/getsentry/sentry-switch",
- target, string.Join("\n", missingFiles.Select(f => $" - {f}"))
- );
- return;
- }
-
- var allFilesPresent = missingFiles.Count == 0;
- if (allFilesPresent)
- {
- logger.LogInfo("{0} native libraries found:\n{1}",
- target, string.Join("\n", existingFiles.Select(f => $" - {f}")));
- importer.SetCompatibleWithPlatform(target, false);
- }
- else
+ if (!SwitchNativeStub.IsInSync(target))
{
- if (nativeSupportEnabled)
- {
- logger.LogWarning(
- "{0} native support is enabled but required files are missing:\n{1}\n" +
- "Build sentry-switch and copy the libraries to the expected locations. " +
- "See: https://github.com/getsentry/sentry-switch",
- target, string.Join("\n", missingFiles.Select(f => $" - {f}"))
- );
- }
- else
- {
- logger.LogDebug("{0} native support is disabled. Enabling stubs (native calls will be no-op).", target);
- }
- importer.SetCompatibleWithPlatform(target, true);
+ throw new BuildFailedException(
+ $"Sentry failed to update the Switch no-op stub at '{SwitchNativeStub.StubPathFor(target)}'. " +
+ "See the errors above, resolve it by hand, and trigger the build again.");
}
- importer.SaveAndReimport();
+ throw new BuildFailedException(
+ "Sentry's Switch no-op stubs in 'Assets/Plugins/Sentry' were out of step with the " +
+ "installed libraries and have been updated. Please trigger the build again.");
}
}
diff --git a/src/Sentry.Unity.Editor/Native/SwitchNativeStub.cs b/src/Sentry.Unity.Editor/Native/SwitchNativeStub.cs
new file mode 100644
index 000000000..50b2b7e4c
--- /dev/null
+++ b/src/Sentry.Unity.Editor/Native/SwitchNativeStub.cs
@@ -0,0 +1,230 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Sentry.Extensibility;
+using UnityEditor;
+
+namespace Sentry.Unity.Editor.Native;
+
+///
+/// Keeps the no-op native stubs in the user's project up to date with the sentry-switch libraries
+/// they have installed.
+///
+///
+///
+/// Only the active build target gains a copy, so projects do not carry a file they have no use for.
+/// Removal is not gated that way, because a stale stub shadows the libraries next to it.
+///
+///
+internal static class SwitchNativeStub
+{
+ internal const string StubFileName = "sentry_native_stubs.c";
+
+ ///
+ /// The sentry-switch libraries the stub stands in for. The watcher below keys off these names.
+ ///
+ internal static readonly string[] LibraryFileNames = ["libsentry.a", "libzstd.a"];
+
+ ///
+ /// The build target's name doubles as the directory name, i.e. `Switch` and `Switch2`.
+ ///
+ internal static string PluginDirectoryFor(BuildTarget target) => $"Assets/Plugins/Sentry/{target}";
+
+ ///
+ /// Both platforms share one stub. The difference is the directory name the stub goes in.
+ ///
+ internal static string[] RequiredFilesFor(BuildTarget target)
+ {
+ var directory = PluginDirectoryFor(target);
+ return LibraryFileNames.Select(library => $"{directory}/{library}").ToArray();
+ }
+
+ internal static string StubPathFor(BuildTarget target) => $"{PluginDirectoryFor(target)}/{StubFileName}";
+
+ ///
+ /// Resolved by enumeration because BuildTarget.Switch2 does not exist in Unity 6000.2 or older.
+ ///
+ internal static IEnumerable Targets => Enum
+ .GetValues(typeof(BuildTarget))
+ .Cast()
+ .Where(target => target.IsSwitchFamily());
+
+ ///
+ /// Whether the stub is needed and matches the packaged version.
+ ///
+ ///
+ /// Checks the content. An older stub might miss new API.
+ ///
+ internal static bool IsInSync(BuildTarget target)
+ {
+ var stubPath = StubPathFor(target);
+ var stubNeeded = RequiredFilesFor(target).Any(file => !File.Exists(file));
+
+ if (!File.Exists(stubPath))
+ {
+ return !stubNeeded;
+ }
+
+ return stubNeeded && Matches(File.ReadAllText(stubPath), File.ReadAllText(TemplatePath()));
+ }
+
+ private static bool Matches(string stub, string template) =>
+ string.Equals(Normalize(stub), Normalize(template), StringComparison.Ordinal);
+
+ private static string Normalize(string content) =>
+ content.Replace("\r\n", "\n").Replace("\r", "\n").TrimEnd();
+
+ [InitializeOnLoadMethod]
+ private static void OnDomainReload() => EditorApplication.delayCall += () => Sync(null);
+
+ ///
+ /// Syncs the SDK with the state of the game. Adds/removes the stub.
+ ///
+ internal static void Sync(IDiagnosticLogger? logger) =>
+ Sync(logger, EditorUserBuildSettings.activeBuildTarget);
+
+ internal static void Sync(IDiagnosticLogger? logger, BuildTarget activeTarget)
+ {
+ // Running inside a domain reload or an asset change callback.
+ logger ??= new UnityLogger(new SentryUnityOptions());
+
+ foreach (var target in Targets)
+ {
+ var missingFiles = RequiredFilesFor(target).Where(file => !File.Exists(file)).ToList();
+
+ if (missingFiles.Count == 0)
+ {
+ RemoveStub(logger, target);
+ }
+ // No reason to put a file in the user's repository for a target they are not building.
+ else if (target == activeTarget)
+ {
+ WarnAboutPartialInstall(logger, target, missingFiles);
+ AddStub(logger, target);
+ }
+ }
+ }
+
+ private static void WarnAboutPartialInstall(
+ IDiagnosticLogger logger, BuildTarget target, List missingFiles)
+ {
+ if (missingFiles.Count == LibraryFileNames.Length)
+ {
+ return;
+ }
+
+ logger.LogWarning(
+ "{0} native support is partially configured. Missing files:\n{1}\n" +
+ "Sentry's no-op stubs are being used instead. Add all required files to enable native " +
+ "support, or remove all of them to silence this warning. " +
+ "Build sentry-switch and copy the libraries to the expected locations. " +
+ "See: https://github.com/getsentry/sentry-switch",
+ target, string.Join("\n", missingFiles.Select(file => $" - {file}")));
+ }
+
+ private static void AddStub(IDiagnosticLogger logger, BuildTarget target)
+ {
+ var stubPath = StubPathFor(target);
+ var template = File.ReadAllText(TemplatePath());
+
+ var existing = File.Exists(stubPath);
+ if (existing && Matches(File.ReadAllText(stubPath), template))
+ {
+ // Manually set the stub's targeted platform.
+ ConfigureImporter(logger, stubPath, target);
+ return;
+ }
+
+ _ = Directory.CreateDirectory(Path.GetDirectoryName(stubPath)!);
+ File.WriteAllText(stubPath, template);
+ // Refresh because the plugin directory itself may be new to the AssetDatabase.
+ AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
+ ConfigureImporter(logger, stubPath, target);
+
+ if (existing)
+ {
+ logger.LogInfo("{0} no-op stubs in '{1}' updated to the version this SDK ships.",
+ target, PluginDirectoryFor(target));
+ return;
+ }
+
+ logger.LogInfo(
+ "{0} native libraries not found in '{1}'. Added no-op stubs.",
+ target, PluginDirectoryFor(target));
+ }
+
+ private static void RemoveStub(IDiagnosticLogger logger, BuildTarget target)
+ {
+ var stubPath = StubPathFor(target);
+ if (!File.Exists(stubPath))
+ {
+ return;
+ }
+
+ if (!AssetDatabase.DeleteAsset(stubPath))
+ {
+ logger.LogError(
+ "Failed to delete '{0}'. Native support might fail at runtime.\n" +
+ "Delete it by hand and rebuild.", stubPath);
+ return;
+ }
+
+ logger.LogInfo("{0} native libraries found in '{1}'. Removed no-op stubs.",
+ target, PluginDirectoryFor(target));
+ }
+
+ ///
+ /// The stub should only target the platform for the directory's name it is in. `.c` targets every platform
+ /// by default.
+ ///
+ private static void ConfigureImporter(IDiagnosticLogger logger, string stubPath, BuildTarget target)
+ {
+ if (AssetImporter.GetAtPath(stubPath) is not PluginImporter importer)
+ {
+ logger.LogError("Failed to get the PluginImporter for '{0}'. Skipping stub configuration.", stubPath);
+ return;
+ }
+
+ if (!importer.GetCompatibleWithAnyPlatform() &&
+ !importer.GetCompatibleWithEditor() &&
+ Targets.All(candidate => importer.GetCompatibleWithPlatform(candidate) == (candidate == target)))
+ {
+ return;
+ }
+
+ importer.SetCompatibleWithAnyPlatform(false);
+ importer.SetCompatibleWithEditor(false);
+ foreach (var candidate in Targets)
+ {
+ importer.SetCompatibleWithPlatform(candidate, candidate == target);
+ }
+
+ importer.SaveAndReimport();
+ }
+
+ private static string TemplatePath() => Path.GetFullPath(Path.Combine(
+ "Packages", SentryPackageInfo.GetName(), "Plugins", "Switch", "SentryStub~", StubFileName));
+}
+
+///
+/// Monitor the imported Assets so we can replace the stubs when necessary.
+///
+internal class SwitchNativeStubWatcher : AssetPostprocessor
+{
+ private static void OnPostprocessAllAssets(
+ string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
+ {
+ var librariesChanged = importedAssets
+ .Concat(deletedAssets)
+ .Concat(movedAssets)
+ .Concat(movedFromAssetPaths)
+ .Any(path => SwitchNativeStub.LibraryFileNames.Any(
+ library => path.EndsWith(library, StringComparison.Ordinal)));
+
+ if (librariesChanged)
+ {
+ EditorApplication.delayCall += () => SwitchNativeStub.Sync(null);
+ }
+ }
+}
diff --git a/src/Sentry.Unity.Native/CFunctions.cs b/src/Sentry.Unity.Native/CFunctions.cs
index e0bef2db9..ec7a27909 100644
--- a/src/Sentry.Unity.Native/CFunctions.cs
+++ b/src/Sentry.Unity.Native/CFunctions.cs
@@ -8,11 +8,7 @@ namespace Sentry.Unity.Native;
internal static class C
{
-#if SENTRY_NATIVE_SWITCH
- private const string SentryLib = "__Internal";
-#else
- private const string SentryLib = "sentry";
-#endif
+ private const string SentryLib = SentryNativeLibrary.Name;
internal static void SetValueIfNotNull(sentry_value_t obj, string key, string? value)
{
diff --git a/src/Sentry.Unity.Native/Sentry.Unity.Native.csproj b/src/Sentry.Unity.Native/Sentry.Unity.Native.csproj
index ca7e5386e..de28b4ec0 100644
--- a/src/Sentry.Unity.Native/Sentry.Unity.Native.csproj
+++ b/src/Sentry.Unity.Native/Sentry.Unity.Native.csproj
@@ -65,4 +65,23 @@
/>
+
+
+
+
+
+
+
diff --git a/src/Sentry.Unity.Native/SentryNative.cs b/src/Sentry.Unity.Native/SentryNative.cs
index c255ada10..edbea2569 100644
--- a/src/Sentry.Unity.Native/SentryNative.cs
+++ b/src/Sentry.Unity.Native/SentryNative.cs
@@ -140,7 +140,7 @@ private static void ReinstallBackend()
}
catch (EntryPointNotFoundException e)
{
- Logger?.LogError(e, "Native dependency not found. Did you delete sentry.dll or move files around?");
+ Logger?.LogError(e, "Native dependency not found. Did you delete '{0}' or move files around?", SentryNativeLibrary.Name);
}
}
}
diff --git a/src/Sentry.Unity.Native/SentryNativeBridge.cs b/src/Sentry.Unity.Native/SentryNativeBridge.cs
index d4cfc3e39..8ebeaa840 100644
--- a/src/Sentry.Unity.Native/SentryNativeBridge.cs
+++ b/src/Sentry.Unity.Native/SentryNativeBridge.cs
@@ -14,11 +14,7 @@ namespace Sentry.Unity.Native;
///
internal static class SentryNativeBridge
{
-#if SENTRY_NATIVE_SWITCH
- private const string SentryLib = "__Internal";
-#else
- private const string SentryLib = "sentry";
-#endif
+ private const string SentryLib = SentryNativeLibrary.Name;
private static IDiagnosticLogger? Logger; // This is also the logger we're forwarding native messages to.
private static bool UseLibC;
@@ -166,7 +162,6 @@ internal static string GetDatabasePath(SentryUnityOptions options, IApplication?
internal static void AppHangPause() => sentry_app_hang_pause();
- // libsentry.so
[DllImport(SentryLib)]
private static extern IntPtr sentry_options_new();
diff --git a/src/Sentry.Unity.Native/SentryNativeLibrary.cs b/src/Sentry.Unity.Native/SentryNativeLibrary.cs
new file mode 100644
index 000000000..2d37b61a1
--- /dev/null
+++ b/src/Sentry.Unity.Native/SentryNativeLibrary.cs
@@ -0,0 +1,20 @@
+namespace Sentry.Unity.Native;
+
+///
+/// The library name this build binds its P/Invokes to.
+///
+///
+/// Desktop cannot use "sentry": Mono probes the calling assembly's own directory first, where it
+/// resolves to the managed `Sentry.dll` on a case-insensitive file system. Android and the consoles
+/// get their library from elsewhere and keep the original name.
+///
+internal static class SentryNativeLibrary
+{
+#if SENTRY_NATIVE_SWITCH
+ internal const string Name = "__Internal";
+#elif SENTRY_NATIVE_ANDROID || SENTRY_NATIVE_PLAYSTATION || SENTRY_NATIVE_XBOX
+ internal const string Name = "sentry";
+#else
+ internal const string Name = "sentry-native";
+#endif
+}
diff --git a/src/Sentry.Unity/Properties/AssemblyInfo.cs b/src/Sentry.Unity/Properties/AssemblyInfo.cs
index 9e8226ca7..3f9e80ca2 100644
--- a/src/Sentry.Unity/Properties/AssemblyInfo.cs
+++ b/src/Sentry.Unity/Properties/AssemblyInfo.cs
@@ -4,6 +4,7 @@
[assembly: InternalsVisibleTo("Sentry.Unity.Native.PlayStation")]
[assembly: InternalsVisibleTo("Sentry.Unity.Native.Switch")]
[assembly: InternalsVisibleTo("Sentry.Unity.Native.Xbox")]
+[assembly: InternalsVisibleTo("Sentry.Unity.Native.Android")]
[assembly: InternalsVisibleTo("Sentry.Unity.Tests")]
[assembly: InternalsVisibleTo("Sentry.Unity.Editor")]
[assembly: InternalsVisibleTo("Sentry.Unity.Editor.Tests")]
diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1
index 5a9b6e7fd..33e6315b0 100644
--- a/test/IntegrationTest/Integration.Tests.ps1
+++ b/test/IntegrationTest/Integration.Tests.ps1
@@ -9,6 +9,7 @@
#
# SENTRY_TEST_APP: path to the test app (APK, executable, .app bundle, WebGL build directory,
# or Xbox packaged build directory containing a .xvc)
+# SENTRY_TEST_SCRIPTING_BACKEND: set to "mono" for a Mono player; empty means IL2CPP
#
# Platform-specific environment variables:
# iOS: SENTRY_IOS_VERSION - iOS simulator version (e.g. "17.0" or "latest")
@@ -442,6 +443,10 @@ Describe "Unity $($env:SENTRY_TEST_PLATFORM) Integration Tests" {
Set-ItResult -Skipped -Because "Source-line assertions are unsupported on $script:Platform"
return
}
+ if ($env:SENTRY_TEST_SCRIPTING_BACKEND -eq "mono") {
+ Set-ItResult -Skipped -Because "line numbers come from the IL2CPP mappings, which a Mono player does not produce"
+ return
+ }
$frame = $runEvent.exception.values[0].stacktrace.frames |
Where-Object { $_.module -eq "IntegrationTester" -and $_.function -eq "ThrowException" } |
diff --git a/test/Scripts.Integration.Test/Editor/Builder.cs b/test/Scripts.Integration.Test/Editor/Builder.cs
index 068ee1d07..f4a967045 100644
--- a/test/Scripts.Integration.Test/Editor/Builder.cs
+++ b/test/Scripts.Integration.Test/Editor/Builder.cs
@@ -9,7 +9,7 @@
public class Builder
{
public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group, BuildOptions buildOptions,
- string defaultBuildPath = "./Builds/")
+ string defaultBuildPath = "./Builds/", ScriptingImplementation scripting = ScriptingImplementation.IL2CPP)
{
Debug.Log("Builder: Starting to build");
@@ -23,19 +23,22 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group,
EditorUserBuildSettings.selectedBuildTargetGroup = group;
EditorUserBuildSettings.development = false;
EditorUserBuildSettings.allowDebugging = false;
- PlayerSettings.SetScriptingBackend(NamedBuildTarget.FromBuildTargetGroup(group), ScriptingImplementation.IL2CPP);
+ PlayerSettings.SetScriptingBackend(NamedBuildTarget.FromBuildTargetGroup(group), scripting);
// Making sure that the app keeps on running in the background. Linux CI is very unhappy with coroutines otherwise.
PlayerSettings.runInBackground = true;
DisableUnityAudio();
DisableProgressiveLightMapper();
- Debug.Log("Builder: Setting IL2CPP generation to OptimizeSpeed");
+ if (scripting == ScriptingImplementation.IL2CPP)
+ {
+ 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(NamedBuildTarget.FromBuildTargetGroup(group), UnityEditor.Build.Il2CppCodeGeneration.OptimizeSpeed);
#elif UNITY_2021_2_OR_NEWER
- EditorUserBuildSettings.il2CppCodeGeneration = UnityEditor.Build.Il2CppCodeGeneration.OptimizeSpeed;
+ EditorUserBuildSettings.il2CppCodeGeneration = UnityEditor.Build.Il2CppCodeGeneration.OptimizeSpeed;
#endif
+ }
Debug.Log("Builder: Configuring code stripping level");
#if UNITY_6000_0_OR_NEWER
@@ -55,7 +58,7 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group,
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 (target != BuildTarget.StandaloneLinux64)
+ if (scripting == ScriptingImplementation.IL2CPP && target != BuildTarget.StandaloneLinux64)
{
PlayerSettings.SetIl2CppCompilerConfiguration(NamedBuildTarget.FromBuildTargetGroup(group), Il2CppCompilerConfiguration.Debug);
}
@@ -123,6 +126,14 @@ public static void BuildWindowsIl2CPPPlayer()
defaultBuildPath: "./Builds/Windows/test.exe");
}
+ [MenuItem("Tools/Builder/Windows Mono")]
+ public static void BuildWindowsMonoPlayer()
+ {
+ Debug.Log("Builder: Building Windows Mono Player");
+ BuildIl2CPPPlayer(BuildTarget.StandaloneWindows64, BuildTargetGroup.Standalone, BuildOptions.StrictMode,
+ defaultBuildPath: "./Builds/Windows/test.exe", scripting: ScriptingImplementation.Mono2x);
+ }
+
[MenuItem("Tools/Builder/macOS")]
public static void BuildMacIl2CPPPlayer()
{
diff --git a/test/Scripts.Integration.Test/globals.ps1 b/test/Scripts.Integration.Test/globals.ps1
index 2c1d033f6..805004bff 100644
--- a/test/Scripts.Integration.Test/globals.ps1
+++ b/test/Scripts.Integration.Test/globals.ps1
@@ -148,6 +148,7 @@ function BuildMethodFor([string] $platform)
"Android-Export" { return "Builder.BuildAndroidIl2CPPProject" }
"MacOS" { return "Builder.BuildMacIl2CPPPlayer" }
"Windows" { return "Builder.BuildWindowsIl2CPPPlayer" }
+ "Windows-Mono" { return "Builder.BuildWindowsMonoPlayer" }
"Linux" { return "Builder.BuildLinuxIl2CPPPlayer" }
"WebGL" { return "Builder.BuildWebGLPlayer" }
"iOS" { return "Builder.BuildIOSProject" }
diff --git a/test/Scripts.Tests/package-release.zip.snapshot b/test/Scripts.Tests/package-release.zip.snapshot
index a25fa106d..a4471d2f5 100644
--- a/test/Scripts.Tests/package-release.zip.snapshot
+++ b/test/Scripts.Tests/package-release.zip.snapshot
@@ -28,6 +28,7 @@ Editor/sentry-cli/sentry-cli-Linux-x86_64.meta
Editor/sentry-cli/sentry-cli-Windows-x86_64.exe
Editor/sentry-cli/sentry-cli-Windows-x86_64.exe.meta
Plugins/Linux/
+Plugins/Switch/
Plugins/Windows/
Plugins/Android.meta
Plugins/iOS.meta
@@ -38,9 +39,9 @@ Plugins/Switch.meta
Plugins/Windows.meta
Plugins/macOS/SentryNativeBridge.m
Plugins/macOS/SentryNativeBridge.m.meta
-Plugins/macOS/SentryNative~/libsentry.dylib.dSYM/
+Plugins/macOS/SentryNative~/libsentry-native.dylib.dSYM/
Plugins/macOS/SentryNative~/sentry-crash.dSYM/
-Plugins/macOS/SentryNative~/libsentry.dylib
+Plugins/macOS/SentryNative~/libsentry-native.dylib
Plugins/macOS/SentryNative~/sentry-crash
Plugins/macOS/SentryNative~/sentry-crash.dSYM/Contents/Resources/
Plugins/macOS/SentryNative~/sentry-crash.dSYM/Contents/Info.plist
@@ -48,12 +49,12 @@ Plugins/macOS/SentryNative~/sentry-crash.dSYM/Contents/Resources/Relocations/
Plugins/macOS/SentryNative~/sentry-crash.dSYM/Contents/Resources/Relocations/aarch64/sentry-crash.yml
Plugins/macOS/SentryNative~/sentry-crash.dSYM/Contents/Resources/Relocations/x86_64/sentry-crash.yml
Plugins/macOS/SentryNative~/sentry-crash.dSYM/Contents/Resources/DWARF/sentry-crash
-Plugins/macOS/SentryNative~/libsentry.dylib.dSYM/Contents/Resources/
-Plugins/macOS/SentryNative~/libsentry.dylib.dSYM/Contents/Info.plist
-Plugins/macOS/SentryNative~/libsentry.dylib.dSYM/Contents/Resources/Relocations/
-Plugins/macOS/SentryNative~/libsentry.dylib.dSYM/Contents/Resources/Relocations/aarch64/libsentry.dylib.yml
-Plugins/macOS/SentryNative~/libsentry.dylib.dSYM/Contents/Resources/Relocations/x86_64/libsentry.dylib.yml
-Plugins/macOS/SentryNative~/libsentry.dylib.dSYM/Contents/Resources/DWARF/libsentry.dylib
+Plugins/macOS/SentryNative~/libsentry-native.dylib.dSYM/Contents/Resources/
+Plugins/macOS/SentryNative~/libsentry-native.dylib.dSYM/Contents/Info.plist
+Plugins/macOS/SentryNative~/libsentry-native.dylib.dSYM/Contents/Resources/Relocations/
+Plugins/macOS/SentryNative~/libsentry-native.dylib.dSYM/Contents/Resources/Relocations/aarch64/libsentry-native.dylib.yml
+Plugins/macOS/SentryNative~/libsentry-native.dylib.dSYM/Contents/Resources/Relocations/x86_64/libsentry-native.dylib.yml
+Plugins/macOS/SentryNative~/libsentry-native.dylib.dSYM/Contents/Resources/DWARF/libsentry-native.dylib
Plugins/macOS/Sentry~/Sentry.dylib
Plugins/macOS/Sentry~/Sentry.dylib.dSYM
Plugins/PS5/sentry_utils.c
@@ -269,29 +270,28 @@ Plugins/iOS/SentryObjC.xcframework~/ios-arm64/dSYMs/SentryObjC.framework.dSYM/Co
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.dbg.so
-Plugins/Linux/SentryNative~/libsentry.so
+Plugins/Linux/SentryNative~/libsentry-native.so
Plugins/Linux/SentryNative~/sentry-crash
Plugins/Linux/SentryNative~/sentry-crash.dbg
Plugins/Linux/Sentry~/libsentry.dbg.so
-Plugins/Linux/Sentry~/libsentry.so
+Plugins/Linux/Sentry~/libsentry-native.so
Plugins/Android/proguard-sentry-unity.pro
Plugins/Android/proguard-sentry-unity.pro.meta
Plugins/Android/Sentry~/sentry-android-core-release.aar
Plugins/Android/Sentry~/sentry-android-ndk-release.aar
Plugins/Android/Sentry~/sentry-native-ndk-release.aar
Plugins/Android/Sentry~/sentry.jar
-Plugins/Switch/sentry_native_stubs.c
-Plugins/Switch/sentry_native_stubs.c.meta
+Plugins/Switch/SentryStub~/sentry_native_stubs.c
Plugins/Windows/SentryNative~/sentry-crash.exe
Plugins/Windows/SentryNative~/sentry-crash.pdb
Plugins/Windows/SentryNative~/sentry-wer.dll
Plugins/Windows/SentryNative~/sentry-wer.pdb
-Plugins/Windows/SentryNative~/sentry.dll
+Plugins/Windows/SentryNative~/sentry-native.dll
Plugins/Windows/SentryNative~/sentry.pdb
Plugins/Windows/Sentry~/crashpad_handler.exe
Plugins/Windows/Sentry~/crashpad_wer.dll
Plugins/Windows/Sentry~/crashpad_wer.pdb
-Plugins/Windows/Sentry~/sentry.dll
+Plugins/Windows/Sentry~/sentry-native.dll
Plugins/Windows/Sentry~/sentry.pdb
Prefabs/SentryUserFeedback.prefab
Prefabs/SentryUserFeedback.prefab.meta
@@ -327,6 +327,10 @@ Runtime/Sentry.Unity.MacOS.dll
Runtime/Sentry.Unity.MacOS.dll.meta
Runtime/Sentry.Unity.MacOS.pdb
Runtime/Sentry.Unity.MacOS.pdb.meta
+Runtime/Sentry.Unity.Native.Android.dll
+Runtime/Sentry.Unity.Native.Android.dll.meta
+Runtime/Sentry.Unity.Native.Android.pdb
+Runtime/Sentry.Unity.Native.Android.pdb.meta
Runtime/Sentry.Unity.Native.dll
Runtime/Sentry.Unity.Native.dll.meta
Runtime/Sentry.Unity.Native.pdb
diff --git a/test/Scripts.Tests/test-plugin-platforms.ps1 b/test/Scripts.Tests/test-plugin-platforms.ps1
new file mode 100644
index 000000000..602beaf8c
--- /dev/null
+++ b/test/Scripts.Tests/test-plugin-platforms.ps1
@@ -0,0 +1,333 @@
+# Pins which platforms every plugin and assembly definition in the package targets.
+#
+# 1. A hardcoded scope per plugin importer and .asmdef. Any drift fails, so changing a scope takes a
+# deliberate edit to the tables below. Guards against a repeat of the platform list inversion,
+# where an allowlist became an exclude list and quietly widened what an assembly shipped to.
+#
+# 2. No managed plugin declaring `DllImport("__Internal")` may target a desktop standalone player.
+# `__Internal` binds at link time against a symbol inside the player executable, which desktop
+# never has, because a native plugin there is always a separate shared library loaded at runtime.
+# Such a build only survives while the UnityLinker strips the unreferenced types.
+#
+# Runs against `package-release.zip` when it exists, because that is what ships. The `package-dev`
+# binaries are whatever was last built there and can describe a different branch entirely. Without
+# the zip, the `package-dev` and `package` trees are checked instead, and the `__Internal` rule only
+# covers assemblies whose binary is present.
+
+$ErrorActionPreference = "Stop"
+
+$projectRoot = (Resolve-Path "$PSScriptRoot/../..").Path
+$packageFile = Join-Path $projectRoot "package-release.zip"
+
+# ---------------------------------------------------------------------------------------------------
+# Pinned expectations. Paths are relative to the package root. Platform names are Unity's own, sorted.
+# ---------------------------------------------------------------------------------------------------
+
+# What the released package must contain. "Any" means the plugin is platform agnostic.
+$ExpectedPluginScopes = @{
+ "Editor/Sentry.Unity.Editor.dll" = "Editor"
+ "Editor/iOS/Sentry.Unity.Editor.iOS.dll" = "Editor"
+ # vsnprintf_sentry, imported as __Internal only under SENTRY_NATIVE_PLAYSTATION. The Switch gets
+ # the same symbol from its own stubs or from sentry-switch, and Xbox goes through msvcrt.
+ "Plugins/PS5/sentry_utils.c" = "PS5"
+ "Plugins/iOS/SentryCxaThrowHook.cpp" = "iOS"
+ # The two bridge sources target nothing on purpose. BuildPostProcess copies whichever one applies
+ # into the generated Xcode project, so enabling a platform here would collide with that copy.
+ "Plugins/iOS/SentryNativeBridge.m" = ""
+ "Plugins/iOS/SentryNativeBridgeNoOp.m" = ""
+ "Plugins/macOS/SentryNativeBridge.m" = "OSXUniversal"
+ "Runtime/Sentry.dll" = "Any"
+ "Runtime/Sentry.Unity.Android.dll" = "Android"
+ "Runtime/Sentry.Unity.MacOS.dll" = "OSXUniversal"
+ "Runtime/Sentry.Unity.Native.PlayStation.dll" = "PS5"
+ "Runtime/Sentry.Unity.Native.Switch.dll" = "Switch, Switch2"
+ "Runtime/Sentry.Unity.Native.Xbox.dll" = "GameCoreScarlett, GameCoreXboxOne"
+ # Android binds to "sentry" from the .aar, desktop to "sentry-native", so they are separate
+ # builds of the same sources. See SentryNativeLibrary.Name.
+ "Runtime/Sentry.Unity.Native.Android.dll" = "Android"
+ "Runtime/Sentry.Unity.Native.dll" = "Linux64, OSXUniversal, Win, Win64"
+ "Runtime/Sentry.Unity.dll" = "Any"
+ # Holds the Cocoa bridge __Internal declarations, shared by the iOS and macOS integrations.
+ "Runtime/Sentry.Unity.iOS.dll" = "iOS, OSXUniversal"
+}
+
+# Demo sources rather than SDK plugins, but they ship inside the package, so they are pinned too.
+$ExpectedSampleScopes = @{
+ "Samples~/unity-of-bugs/Scripts/NativeSupport/CPlugin.c" = "Android, Any, iOS, Linux64, Lumin, OSXUniversal, tvOS, WebGL, Win, Win64"
+ "Samples~/unity-of-bugs/Scripts/NativeSupport/CppPlugin.cpp" = "Android, Any, iOS, Linux64, Lumin, OSXUniversal, tvOS, WebGL, Win, Win64"
+ "Samples~/unity-of-bugs/Scripts/NativeSupport/JavaScriptPlugin.jslib" = "WebGL"
+ "Samples~/unity-of-bugs/Scripts/NativeSupport/KotlinPlugin.kt" = "Android"
+ "Samples~/unity-of-bugs/Scripts/NativeSupport/ObjectiveCPlugin.m" = "iOS, tvOS"
+}
+
+# The third party assemblies scripts/alias-assemblies.ps1 renames into the `Sentry.` namespace. They
+# are managed and platform agnostic, so they carry Unity's folder default. Matched by pattern because
+# the set turns over with every sentry-dotnet bump while the scope never does. The patterns cover the
+# aliased prefixes only, so a new first party assembly still has to be pinned by name above.
+$AliasedDependencyScopes = @(
+ @{ Pattern = '^Editor/Sentry\.(Microsoft|Mono)\..*\.dll$' ; Scope = "Editor" }
+ @{ Pattern = '^Runtime/Sentry\.(Microsoft|System)\..*\.dll$'; Scope = "Any" }
+)
+
+# How package-dev deviates. The dev package keeps the test assemblies, which scripts/pack.ps1 excludes
+# from the release, and the iOS bridge stays editor-loadable for the editor-only
+# Sentry.Unity.iOS.Tests assembly that references it.
+$DevOnlyPluginScopes = @{
+ "Runtime/Sentry.Unity.iOS.dll" = "Editor, iOS, OSXUniversal"
+ "Tests/Editor/Sentry.Unity.Editor.Tests.dll" = "Editor"
+ "Tests/Editor/Sentry.Unity.Editor.iOS.Tests.dll" = "Editor"
+ "Tests/Runtime/Sentry.Unity.Android.Tests.dll" = "Editor"
+ "Tests/Runtime/Sentry.Unity.Tests.dll" = "Editor"
+ "Tests/Runtime/Sentry.Unity.iOS.Tests.dll" = "Editor"
+}
+
+# Files the package directory is allowed to override in the release, each validated against the
+# release table above.
+$ExpectedReleaseOverrides = @(
+ "Runtime/Sentry.Unity.iOS.dll"
+)
+
+# Assembly definitions. An empty include list plus an exclude list means "every platform except
+# these", so the exclude list is the thing that must not drift unnoticed.
+$ExpectedAsmdefs = @{
+ "Runtime/io.sentry.unity.runtime.asmdef" = @{
+ include = ""
+ exclude = "CloudRendering, EmbeddedLinux, PS4, tvOS, XboxOne"
+ }
+ "Editor/io.sentry.unity.editor.asmdef" = @{
+ include = "Editor"
+ exclude = ""
+ }
+}
+
+$DevOnlyAsmdefs = @{
+ "Runtime/io.sentry.unity.dev.runtime.asmdef" = @{
+ include = ""
+ exclude = "CloudRendering, EmbeddedLinux, PS4, tvOS, XboxOne"
+ }
+ "Editor/io.sentry.unity.dev.editor.asmdef" = @{
+ include = "Editor"
+ exclude = ""
+ }
+}
+
+# Platforms where Unity loads native code exclusively as a separate shared library.
+$DynamicOnlyPlatforms = @("Win", "Win64", "Linux64")
+
+# ---------------------------------------------------------------------------------------------------
+# Parsing
+# ---------------------------------------------------------------------------------------------------
+
+# Sorted, comma separated list of the platforms a plugin importer enables. Handles both .meta
+# dialects: the legacy "- first:/second:" list and the newer platform-keyed map.
+function Get-EnabledPlatforms([string]$metaText) {
+ $platforms = @()
+ $current = $null
+ $expectKey = $false
+
+ foreach ($line in ($metaText -split "`r?`n")) {
+ if ($line -match '^\s*-\s*first:\s*$') {
+ $expectKey = $true
+ continue
+ }
+ if ($expectKey) {
+ # "Standalone: Win64", "iPhone: iOS", "Editor: Editor", ": Any" or "Any:"
+ if ($line -match '^\s*(.*?):\s*(\S*)\s*$') {
+ $current = if ($Matches[2]) { $Matches[2] } else { $Matches[1] }
+ }
+ $expectKey = $false
+ continue
+ }
+ if ($line -match '^\s*second:\s*$') { continue }
+ if ($line -match '^\s{4}(\S[^:]*):\s*$') {
+ $current = $Matches[1]
+ continue
+ }
+ if ($line -match '^\s*enabled:\s*(\d)\s*$' -and $current) {
+ if ($Matches[1] -eq '1') { $platforms += $current }
+ $current = $null
+ }
+ }
+
+ return (($platforms | Sort-Object -Unique) -join ", ")
+}
+
+# The pinned scope for an aliased third party assembly, or $null when the path is not one.
+function Get-AliasedDependencyScope([string]$path) {
+ foreach ($rule in $script:AliasedDependencyScopes) {
+ if ($path -match $rule.Pattern) { return $rule.Scope }
+ }
+ return $null
+}
+
+function Get-AsmdefPlatforms([string]$asmdefText) {
+ $json = $asmdefText | ConvertFrom-Json
+ return @{
+ include = ((@($json.includePlatforms) | Where-Object { $_ } | Sort-Object) -join ", ")
+ exclude = ((@($json.excludePlatforms) | Where-Object { $_ } | Sort-Object) -join ", ")
+ }
+}
+
+# path -> @{ Text; Bytes } for every .meta, .asmdef and .dll, from the zip or from a directory.
+function Get-PackageFiles($source, [bool]$fromZip) {
+ $files = @{}
+
+ if ($fromZip) {
+ Add-Type -AssemblyName System.IO.Compression.FileSystem
+ $zip = [IO.Compression.ZipFile]::OpenRead($source)
+ try {
+ foreach ($entry in $zip.Entries) {
+ if ($entry.FullName -notmatch '\.(meta|asmdef|dll)$') { continue }
+ $stream = New-Object IO.MemoryStream
+ $entry.Open().CopyTo($stream)
+ $bytes = $stream.ToArray()
+ $stream.Dispose()
+ $files[$entry.FullName.Replace("\", "/")] = @{
+ Text = [Text.Encoding]::UTF8.GetString($bytes)
+ Bytes = $bytes
+ }
+ }
+ }
+ finally {
+ $zip.Dispose()
+ }
+ return $files
+ }
+
+ foreach ($file in Get-ChildItem -Path $source -Recurse -File) {
+ if ($file.Extension -notin ".meta", ".asmdef", ".dll") { continue }
+ $bytes = [IO.File]::ReadAllBytes($file.FullName)
+ $files[$file.FullName.Substring($source.Length + 1).Replace("\", "/")] = @{
+ Text = [Text.Encoding]::UTF8.GetString($bytes)
+ Bytes = $bytes
+ }
+ }
+
+ return $files
+}
+
+# ---------------------------------------------------------------------------------------------------
+# Checks
+# ---------------------------------------------------------------------------------------------------
+
+$failures = [Collections.ArrayList]::new()
+$scopesChecked = 0
+$importsChecked = 0
+
+function Test-Tree($label, $files, $expectedScopes, $expectedAsmdefs) {
+ $seen = @{}
+
+ foreach ($path in ($files.Keys | Sort-Object)) {
+ $entry = $files[$path]
+
+ if ($path -like "*.meta") {
+ if ($entry.Text -notmatch "PluginImporter") { continue }
+ $described = $path -replace '\.meta$', ''
+ $actual = Get-EnabledPlatforms $entry.Text
+ $seen[$described] = $true
+
+ $expected = if ($expectedScopes.ContainsKey($described)) { $expectedScopes[$described] } else { Get-AliasedDependencyScope $described }
+ if ($null -eq $expected) {
+ [void]$script:failures.Add("$label : '$described' is not in the expected table, it enables '$actual'")
+ continue
+ }
+
+ $script:scopesChecked++
+ if ($actual -ne $expected) {
+ [void]$script:failures.Add("$label : '$described' enables '$actual', expected '$expected'")
+ }
+
+ # The rule that holds no matter what the table says. Needs the binary, which is present in
+ # the packed artifact and in a built working tree.
+ $binary = $files[$described]
+ if ($binary -and $binary.Bytes.Length -gt 0 -and
+ [Text.Encoding]::ASCII.GetString($binary.Bytes).Contains("__Internal")) {
+ $script:importsChecked++
+ $enabled = $actual -split ",\s*"
+ foreach ($platform in $script:DynamicOnlyPlatforms) {
+ if ($enabled -contains $platform) {
+ [void]$script:failures.Add("$label : '$described' declares __Internal imports and targets '$platform', which cannot link them")
+ }
+ }
+ }
+ continue
+ }
+
+ if ($path -like "*.asmdef") {
+ if (-not $expectedAsmdefs.ContainsKey($path)) {
+ [void]$script:failures.Add("$label : assembly definition '$path' is not in the expected table")
+ continue
+ }
+ $actual = Get-AsmdefPlatforms $entry.Text
+ $want = $expectedAsmdefs[$path]
+ $script:scopesChecked++
+ if ($actual.include -ne $want.include) {
+ [void]$script:failures.Add("$label : '$path' includePlatforms is '$($actual.include)', expected '$($want.include)'")
+ }
+ if ($actual.exclude -ne $want.exclude) {
+ [void]$script:failures.Add("$label : '$path' excludePlatforms is '$($actual.exclude)', expected '$($want.exclude)'")
+ }
+ }
+ }
+
+ foreach ($expected in ($expectedScopes.Keys | Sort-Object)) {
+ if (-not $seen.ContainsKey($expected)) {
+ [void]$script:failures.Add("$label : expected plugin '$expected' is missing")
+ }
+ }
+
+ foreach ($expected in ($expectedAsmdefs.Keys | Sort-Object)) {
+ if (-not $files.ContainsKey($expected)) {
+ [void]$script:failures.Add("$label : expected assembly definition '$expected' is missing")
+ }
+ }
+}
+
+if (Test-Path -Path $packageFile) {
+ Write-Host "Validating $packageFile"
+ $files = Get-PackageFiles $packageFile $true
+ if ($files.Count -eq 0) {
+ Write-Host "No .meta, .asmdef or .dll entries found in the package." -ForegroundColor Yellow
+ exit 1
+ }
+ $releaseScopes = @{}
+ foreach ($pair in $ExpectedPluginScopes.GetEnumerator()) { $releaseScopes[$pair.Key] = $pair.Value }
+ foreach ($pair in $ExpectedSampleScopes.GetEnumerator()) { $releaseScopes[$pair.Key] = $pair.Value }
+ Test-Tree "release" $files $releaseScopes $ExpectedAsmdefs
+}
+else {
+ Write-Host "'$packageFile' not found - validating the package-dev and package trees instead"
+
+ $devRoot = Join-Path $projectRoot "package-dev"
+ $overrideRoot = Join-Path $projectRoot "package"
+ foreach ($root in @($devRoot, $overrideRoot)) {
+ if (-not (Test-Path -Path $root)) {
+ Write-Host "'$root' not found." -ForegroundColor Yellow
+ exit 1
+ }
+ }
+
+ # package-dev carries the test assemblies and the editor-loadable iOS bridge.
+ $devScopes = @{}
+ foreach ($pair in $ExpectedPluginScopes.GetEnumerator()) { $devScopes[$pair.Key] = $pair.Value }
+ foreach ($pair in $DevOnlyPluginScopes.GetEnumerator()) { $devScopes[$pair.Key] = $pair.Value }
+ Test-Tree "package-dev" (Get-PackageFiles $devRoot $false) $devScopes $DevOnlyAsmdefs
+
+ # The package directory overrides the release, so whatever it holds must match the release table.
+ $overrideScopes = @{}
+ foreach ($path in $ExpectedReleaseOverrides) { $overrideScopes[$path] = $ExpectedPluginScopes[$path] }
+ Test-Tree "package" (Get-PackageFiles $overrideRoot $false) $overrideScopes $ExpectedAsmdefs
+}
+
+if ($failures.Count -gt 0) {
+ Write-Host "Platform scopes do not match the pinned expectations:" -ForegroundColor Yellow
+ foreach ($failure in $failures) { Write-Host " $failure" -ForegroundColor Red }
+ Write-Host ""
+ Write-Host "If the change is intended, update the tables at the top of this script in the same" -ForegroundColor Yellow
+ Write-Host "commit, so the new scope gets reviewed. A managed plugin declaring __Internal imports" -ForegroundColor Yellow
+ Write-Host "can never target Win, Win64 or Linux64, whatever the table says." -ForegroundColor Yellow
+ exit 3
+}
+
+Write-Host "Pinned $scopesChecked platform scope(s); verified __Internal imports on $importsChecked assembly(ies)." -ForegroundColor Green
+exit 0
diff --git a/test/Sentry.Unity.Editor.Tests/Native/SwitchNativeStubTests.cs b/test/Sentry.Unity.Editor.Tests/Native/SwitchNativeStubTests.cs
index a9b7371eb..9fd3ed0cd 100644
--- a/test/Sentry.Unity.Editor.Tests/Native/SwitchNativeStubTests.cs
+++ b/test/Sentry.Unity.Editor.Tests/Native/SwitchNativeStubTests.cs
@@ -12,13 +12,18 @@ namespace Sentry.Unity.Editor.Tests.Native;
public class SwitchNativeStubTests
{
+ private static string PackageRoot() => Path.GetFullPath(Path.Combine(
+ Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "..", ".."));
+
+ private static string StubTemplatePath() =>
+ Path.Combine(PackageRoot(), "Plugins", "Switch", "SentryStub~", SwitchNativeStub.StubFileName);
+
[Test]
public void Stub_ContainsEverySwitchNativeBinding()
{
- var packageRoot = Path.GetFullPath(Path.Combine(
- Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "..", ".."));
+ var packageRoot = PackageRoot();
var switchAssemblyPath = Path.Combine(packageRoot, "Runtime", "Sentry.Unity.Native.Switch.dll");
- var stubPath = Path.Combine(packageRoot, "Plugins", "Switch", "sentry_native_stubs.c");
+ var stubPath = StubTemplatePath();
Assert.That(File.Exists(switchAssemblyPath), Is.True, $"Switch assembly not found at {switchAssemblyPath}");
Assert.That(File.Exists(stubPath), Is.True, $"Switch stubs not found at {stubPath}");
@@ -48,7 +53,7 @@ public void Stub_ContainsEverySwitchNativeBinding()
[Test]
public void RequiredFilesFor_Switch_ProbesTheSwitchPluginDirectory()
{
- var requiredFiles = SwitchNativePluginBuildPreProcess.RequiredFilesFor(BuildTarget.Switch);
+ var requiredFiles = SwitchNativeStub.RequiredFilesFor(BuildTarget.Switch);
Assert.That(requiredFiles, Is.EquivalentTo(new[]
{
@@ -58,8 +63,29 @@ public void RequiredFilesFor_Switch_ProbesTheSwitchPluginDirectory()
}
///
- /// Switch 2 is resolved by name because BuildTarget.Switch2 does not exist on the Unity versions the
- /// SDK still supports, so this parses the member instead of referencing it and skips where it is unavailable.
+ /// One copy per target, so a project can have real native support on one Switch generation and
+ /// stubs on the other.
+ ///
+ [Test]
+ public void StubPathFor_SitsBesideTheLibrariesItReplaces()
+ {
+ var stubPath = SwitchNativeStub.StubPathFor(BuildTarget.Switch);
+
+ Assert.That(stubPath, Is.EqualTo("Assets/Plugins/Sentry/Switch/sentry_native_stubs.c"));
+ Assert.That(SwitchNativeStub.RequiredFilesFor(BuildTarget.Switch),
+ Has.All.StartsWith(SwitchNativeStub.PluginDirectoryFor(BuildTarget.Switch)));
+ }
+
+ [Test]
+ public void Targets_CoverTheSwitchFamilyOnly()
+ {
+ Assert.That(SwitchNativeStub.Targets, Does.Contain(BuildTarget.Switch));
+ Assert.That(SwitchNativeStub.Targets, Has.All.Matches(target => target.IsSwitchFamily()));
+ }
+
+ ///
+ /// Resolved by name because BuildTarget.Switch2 does not exist on every Unity version the
+ /// SDK supports.
///
[Test]
public void RequiredFilesFor_Switch2_ProbesTheSwitch2PluginDirectory()
@@ -69,7 +95,7 @@ public void RequiredFilesFor_Switch2_ProbesTheSwitch2PluginDirectory()
Assert.Ignore("This Unity version predates 'BuildTarget.Switch2'.");
}
- var requiredFiles = SwitchNativePluginBuildPreProcess.RequiredFilesFor(switch2);
+ var requiredFiles = SwitchNativeStub.RequiredFilesFor(switch2);
Assert.That(requiredFiles, Is.EquivalentTo(new[]
{