diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94ca1885c..f9a4c48cc 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 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 b76d49803..01aa1d6d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,17 @@ - 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 +- Bump Native SDK from v0.16.5 to v0.16.6 ([#2839](https://github.com/getsentry/sentry-unity/pull/2839)) + - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0166) + - [diff](https://github.com/getsentry/sentry-native/compare/0.16.5...0.16.6) +- Bump Cocoa SDK from v9.27.0 to v9.28.0 ([#2840](https://github.com/getsentry/sentry-unity/pull/2840)) + - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#9280) + - [diff](https://github.com/getsentry/sentry-cocoa/compare/9.27.0...9.28.0) - Bump Java SDK from v8.55.0 to v8.56.0 ([#2841](https://github.com/getsentry/sentry-unity/pull/2841)) - [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#8560) - [diff](https://github.com/getsentry/sentry-java/compare/8.55.0...8.56.0) diff --git a/docs/agent-guides/platform-native.md b/docs/agent-guides/platform-native.md index 6cfd76150..a51899c62 100644 --- a/docs/agent-guides/platform-native.md +++ b/docs/agent-guides/platform-native.md @@ -32,7 +32,10 @@ Experimental native modes raise minimum shutdown timeout to 10 seconds. ## Console Plugins - PS5/Xbox libraries are user-supplied: `Assets/Plugins/Sentry/{PS5,XSX,XB1}/`. -- Switch needs user-supplied static `libsentry.a` and `libzstd.a`; none uses shipped no-op stubs, partial installation is an error. +- Switch needs user-supplied static `libsentry.a` and `libzstd.a` per target in `Assets/Plugins/Sentry/{Switch,Switch2}`. It is the only platform that binds `__Internal`, so a missing library is a link error, which is why it alone ships stubs. +- `SwitchNativeStub` copies `Plugins/Switch/SentryStub~/sentry_native_stubs.c` into the user's plugin directory while the libraries are missing and deletes it once they arrive. Copying is limited to the active build target; deletion is not, because a stale stub shadows the libraries beside it. +- The template lives behind a `~` so the AssetDatabase never imports it. Importer settings cannot be written to an immutable package, so it must not become a package asset. +- A copy whose content differs from the template, line endings normalized away, is rewritten from it. That is what refreshes already-copied stubs on upgrade, so editing the template is all a stub change takes. `SwitchNativeStubTests.Stub_ContainsEverySwitchNativeBinding` keeps the template covering every `__Internal` entry point. - Console and Android assemblies compile separately with platform defines. Chained `Csc` targets in `Sentry.Unity.Native.csproj`. ## Tests diff --git a/modules/sentry-cocoa b/modules/sentry-cocoa index aaf96c4a4..61e8cb024 160000 --- a/modules/sentry-cocoa +++ b/modules/sentry-cocoa @@ -1 +1 @@ -Subproject commit aaf96c4a417d30208b3c25d90910d6156ddc973d +Subproject commit 61e8cb02434b26fb34c58126d883e5663cfde238 diff --git a/modules/sentry-native b/modules/sentry-native index 1c935e87c..672b86c77 160000 --- a/modules/sentry-native +++ b/modules/sentry-native @@ -1 +1 @@ -Subproject commit 1c935e87ce24d4697ac4710278269237be17e006 +Subproject commit 672b86c77c1864e1c0b5e72eefadc078e30ef700 diff --git a/package-dev/Plugins/PS5/sentry_utils.c.meta b/package-dev/Plugins/PS5/sentry_utils.c.meta index 36d3e4a3b..3e19daff0 100644 --- a/package-dev/Plugins/PS5/sentry_utils.c.meta +++ b/package-dev/Plugins/PS5/sentry_utils.c.meta @@ -18,12 +18,12 @@ PluginImporter: settings: Exclude Android: 1 Exclude Editor: 1 - Exclude Linux64: 0 + Exclude Linux64: 1 Exclude OSXUniversal: 1 Exclude PS5: 0 Exclude WebGL: 1 - Exclude Win: 0 - Exclude Win64: 0 + Exclude Win: 1 + Exclude Win64: 1 Exclude iOS: 1 Exclude tvOS: 1 - first: @@ -95,13 +95,13 @@ PluginImporter: - first: GameCoreScarlett: GameCoreScarlett second: - enabled: 1 + enabled: 0 settings: CPU: AnyCPU - first: GameCoreXboxOne: GameCoreXboxOne second: - enabled: 1 + enabled: 0 settings: CPU: AnyCPU - first: diff --git a/package-dev/Plugins/Switch/sentry_native_stubs.c b/package-dev/Plugins/Switch/SentryStub~/sentry_native_stubs.c similarity index 89% rename from package-dev/Plugins/Switch/sentry_native_stubs.c rename to package-dev/Plugins/Switch/SentryStub~/sentry_native_stubs.c index 57ffbf7d8..2d4554ebb 100644 --- a/package-dev/Plugins/Switch/sentry_native_stubs.c +++ b/package-dev/Plugins/Switch/SentryStub~/sentry_native_stubs.c @@ -1,21 +1,14 @@ /* * Sentry Switch Stubs * - * No-op stub implementations for sentry-native and Switch helper functions. - * These stubs are used when the user has not provided the actual sentry-switch - * native library, allowing the SDK to compile and run without native crash support. + * No-op stubs for the sentry-switch bindings. sentry-switch is distributed under NDA, so without + * them the player fails to link for anyone who does not have it. * - * When the real sentry-switch library is provided by the user at: - * Assets/Plugins/Sentry/Switch/libsentry.a - * Assets/Plugins/Sentry/Switch/SentrySwitchHelpers.cpp + * `SwitchNativeStub` copies this file into the user's project while the real libraries are missing. + * Edit the original here; a copy that differs from it is rewritten from this file. * - * This stub file will be automatically disabled by the build preprocessor, - * and the real library will be linked instead. - * - * All functions here are no-ops that return safe default values. - * The SDK will appear to initialize successfully, but native features - * (crash reporting, native scope sync) will silently do nothing. - * Managed Sentry features continue to work normally. + * `sentry_init` returns failure so `SentryNativeSwitch` reports native support as unavailable + * instead of silently reporting nothing. Managed Sentry features are unaffected. */ #include @@ -49,9 +42,10 @@ int sentry_init(void* options) return -1; } -void sentry_close(void) +int sentry_close(void) { - /* No-op */ + /* Success, matching sentry-native's `int sentry_close(void)` */ + return 0; } /* @@ -251,9 +245,11 @@ sentry_value_t sentry_value_get_by_key(sentry_value_t value, const char* key) return SENTRY_VALUE_NULL; } -void sentry_value_decref(sentry_value_t value) +int sentry_value_decref(sentry_value_t value) { + /* Refcount reached zero, matching sentry-native's `int sentry_value_decref(sentry_value_t)` */ (void)value; + return 0; } /* @@ -352,9 +348,10 @@ int sentry_clear_crashed_last_run(void) return 0; } -void sentry_reinstall_backend(void) +int sentry_reinstall_backend(void) { - /* No-op */ + /* Success, matching sentry-native's `int sentry_reinstall_backend(void)` */ + return 0; } void sentry_app_hang_heartbeat(void) diff --git a/package-dev/Plugins/iOS/SentryNativeBridge.m.meta b/package-dev/Plugins/iOS/SentryNativeBridge.m.meta index 42ac109e5..db6a0427f 100644 --- a/package-dev/Plugins/iOS/SentryNativeBridge.m.meta +++ b/package-dev/Plugins/iOS/SentryNativeBridge.m.meta @@ -79,7 +79,7 @@ PluginImporter: - first: tvOS: tvOS second: - enabled: 1 + enabled: 0 settings: {} userData: assetBundleName: diff --git a/package-dev/Plugins/iOS/SentryNativeBridgeNoOp.m.meta b/package-dev/Plugins/iOS/SentryNativeBridgeNoOp.m.meta index 11c07bd14..33a3bd416 100644 --- a/package-dev/Plugins/iOS/SentryNativeBridgeNoOp.m.meta +++ b/package-dev/Plugins/iOS/SentryNativeBridgeNoOp.m.meta @@ -71,7 +71,7 @@ PluginImporter: - first: tvOS: tvOS second: - enabled: 1 + enabled: 0 settings: {} userData: assetBundleName: diff --git a/package-dev/Plugins/macOS/SentryNativeBridge.m.meta b/package-dev/Plugins/macOS/SentryNativeBridge.m.meta index 43069dbda..197c1277c 100644 --- a/package-dev/Plugins/macOS/SentryNativeBridge.m.meta +++ b/package-dev/Plugins/macOS/SentryNativeBridge.m.meta @@ -79,7 +79,7 @@ PluginImporter: - first: tvOS: tvOS second: - enabled: 1 + enabled: 0 settings: {} userData: assetBundleName: diff --git a/package-dev/Runtime/Sentry.Unity.iOS.dll.meta b/package-dev/Runtime/Sentry.Unity.iOS.dll.meta index a279b2b75..647eb36e8 100644 --- a/package-dev/Runtime/Sentry.Unity.iOS.dll.meta +++ b/package-dev/Runtime/Sentry.Unity.iOS.dll.meta @@ -18,11 +18,11 @@ PluginImporter: settings: Exclude Android: 1 Exclude Editor: 0 - Exclude Linux64: 0 + Exclude Linux64: 1 Exclude OSXUniversal: 0 Exclude WebGL: 1 - Exclude Win: 0 - Exclude Win64: 0 + Exclude Win: 1 + Exclude Win64: 1 Exclude iOS: 0 - first: Android: Android @@ -46,7 +46,7 @@ PluginImporter: - first: Standalone: Linux64 second: - enabled: 1 + enabled: 0 settings: CPU: None - first: @@ -58,13 +58,13 @@ PluginImporter: - first: Standalone: Win second: - enabled: 1 + enabled: 0 settings: CPU: None - first: Standalone: Win64 second: - enabled: 1 + enabled: 0 settings: CPU: None - first: diff --git a/package-dev/Plugins/Switch/sentry_native_stubs.c.meta b/package/Runtime/Sentry.Unity.iOS.dll.meta similarity index 59% rename from package-dev/Plugins/Switch/sentry_native_stubs.c.meta rename to package/Runtime/Sentry.Unity.iOS.dll.meta index c72bd14c2..a14c1e023 100644 --- a/package-dev/Plugins/Switch/sentry_native_stubs.c.meta +++ b/package/Runtime/Sentry.Unity.iOS.dll.meta @@ -1,8 +1,8 @@ fileFormatVersion: 2 -guid: 3e53c83de0e67254dbada2456cd849ba +guid: 7b1845f97d56c487bb20875e78b23bb7 PluginImporter: externalObjects: {} - serializedVersion: 3 + serializedVersion: 2 iconMap: {} executionOrder: {} defineConstraints: [] @@ -11,55 +11,72 @@ PluginImporter: isExplicitlyReferenced: 0 validateReferences: 1 platformData: - Android: - enabled: 0 - settings: - AndroidLibraryDependee: UnityLibrary - AndroidSharedLibraryType: Executable - CPU: ARMv7 - Any: + - first: + : Any + second: enabled: 0 settings: Exclude Android: 1 Exclude Editor: 1 Exclude Linux64: 1 - Exclude OSXUniversal: 1 - Exclude Switch: 0 - Exclude Switch2: 0 + Exclude OSXUniversal: 0 Exclude WebGL: 1 Exclude Win: 1 Exclude Win64: 1 - Exclude iOS: 1 - Editor: + Exclude iOS: 0 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: enabled: 0 settings: CPU: AnyCPU DefaultValueInitialized: true OS: AnyOS - Linux64: + - first: + Standalone: Linux64 + second: enabled: 0 settings: CPU: None - OSXUniversal: - enabled: 0 - settings: - CPU: None - Switch: - enabled: 1 - settings: {} - Switch2: + - first: + Standalone: OSXUniversal + second: enabled: 1 - settings: {} - Win: + settings: + CPU: AnyCPU + - first: + Standalone: Win + second: enabled: 0 settings: CPU: None - Win64: + - first: + Standalone: Win64 + second: enabled: 0 settings: CPU: None - iOS: + - first: + Windows Store Apps: WindowsStoreApps + second: enabled: 0 + settings: + CPU: AnyCPU + - first: + iPhone: iOS + second: + enabled: 1 settings: AddToEmbeddedBinaries: false CPU: AnyCPU @@ -67,4 +84,4 @@ PluginImporter: FrameworkDependencies: userData: assetBundleName: - assetBundleVariant: \ No newline at end of file + assetBundleVariant: diff --git a/scripts/unity-versions.json b/scripts/unity-versions.json index 808dde353..02a765c8f 100644 --- a/scripts/unity-versions.json +++ b/scripts/unity-versions.json @@ -12,8 +12,8 @@ "changeset": "dacc44548933" }, "6000.3": { - "version": "6000.3.23f1", - "changeset": "09d2ecc7fb28" + "version": "6000.3.24f1", + "changeset": "4e7b9b5b6244" }, "6000.5": { "version": "6000.5.0f1", 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/test/Scripts.Tests/package-release.zip.snapshot b/test/Scripts.Tests/package-release.zip.snapshot index e65f35846..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 @@ -97,6 +98,7 @@ Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framew Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framework/Headers/SentryObjCFeedbackSource.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framework/Headers/SentryObjCFrame.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framework/Headers/SentryObjCGeo.h +Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framework/Headers/SentryObjCHint.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framework/Headers/SentryObjCHttpStatusCodeRange.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framework/Headers/SentryObjCHub.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framework/Headers/SentryObjCId.h @@ -131,6 +133,8 @@ Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framew Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framework/Headers/SentryObjCNSError.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framework/Headers/SentryObjCOptions.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framework/Headers/SentryObjCPrivateSDKOnly.h +Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framework/Headers/SentryObjCProfileLifecycle.h +Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framework/Headers/SentryObjCProfileOptions.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framework/Headers/SentryObjCRedactRegionType.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framework/Headers/SentryObjCReplayApi.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64_x86_64-simulator/SentryObjC.framework/Headers/SentryObjCReplayOptions.h @@ -195,6 +199,7 @@ Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/Sentr Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/SentryObjCFeedbackSource.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/SentryObjCFrame.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/SentryObjCGeo.h +Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/SentryObjCHint.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/SentryObjCHttpStatusCodeRange.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/SentryObjCHub.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/SentryObjCId.h @@ -229,6 +234,8 @@ Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/Sentr Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/SentryObjCNSError.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/SentryObjCOptions.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/SentryObjCPrivateSDKOnly.h +Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/SentryObjCProfileLifecycle.h +Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/SentryObjCProfileOptions.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/SentryObjCRedactRegionType.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/SentryObjCReplayApi.h Plugins/iOS/SentryObjC.xcframework~/ios-arm64/SentryObjC.framework/Headers/SentryObjCReplayOptions.h @@ -274,8 +281,7 @@ 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 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[] {