diff --git a/CHANGELOG.md b/CHANGELOG.md index 319349246..742bedb03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes - Fixed a `NoSuchFieldError` during initialization on Android when setting the `sample rate`. ([#2838](https://github.com/getsentry/sentry-unity/issues/2838)) +- Fixed a potential startup crash when targeting Android. The SDK would still set up native support even without a DSN provided. This would cause `sentry-java` to auto-initialize during startup via the `SentryInitProvider`. The SDK now verifies the presence of a DSN at build time, skipping native support when it's missing and logs accordingly. ([#2846](https://github.com/getsentry/sentry-unity/issues/2846)) ## 4.10.0 diff --git a/src/Sentry.Unity.Editor/Android/AndroidManifestConfiguration.cs b/src/Sentry.Unity.Editor/Android/AndroidManifestConfiguration.cs index 85c5052d5..b4fdf20f6 100644 --- a/src/Sentry.Unity.Editor/Android/AndroidManifestConfiguration.cs +++ b/src/Sentry.Unity.Editor/Android/AndroidManifestConfiguration.cs @@ -48,6 +48,8 @@ public class AndroidManifestConfiguration private readonly bool _isDevelopmentBuild; private readonly ScriptingImplementation _scriptingImplementation; + private bool? _androidSdkEnabled; + public AndroidManifestConfiguration() : this( SentryScriptableObject.LoadOptions, @@ -111,43 +113,60 @@ public void OnPostGenerateGradleAndroidProject(string basePath) SetupProguard(gradleProjectPath); } - internal void ModifyManifest(string basePath) - { - var manifestPath = GetManifestPath(basePath); - if (!File.Exists(manifestPath)) - { - throw new FileNotFoundException("Can't configure native Android SDK nor set auto-init:false.", - manifestPath); - } + /// + /// Whether this build ships the Android SDK. Shared with because an app that + /// carries `sentry-android-core` without the matching manifest entries auto-initializes without a DSN, + /// which crashes it on startup. + /// + private bool AndroidSdkEnabled => _androidSdkEnabled ??= EvaluateAndroidSdkEnabled(); - var enableNativeSupport = true; + private bool EvaluateAndroidSdkEnabled() + { if (_options is null) { _logger.LogWarning("Android native support disabled because Sentry has not been configured. " + "You can do that through the editor: {0}", SentryWindow.EditorMenuPath); - enableNativeSupport = false; + return false; } - else if (!_options.IsValid()) + + if (!_options.IsValid()) { _logger.LogDebug("Android native support disabled."); - enableNativeSupport = false; + return false; } - else if (!_options.AndroidNativeSupportEnabled) + + if (!_options.AndroidNativeSupportEnabled) { _logger.LogDebug("Android native support disabled through the options."); - enableNativeSupport = false; + return false; + } + + return true; + } + + internal void ModifyManifest(string basePath) + { + var manifestPath = GetManifestPath(basePath); + if (!File.Exists(manifestPath)) + { + throw new FileNotFoundException("Can't configure native Android SDK nor set auto-init:false.", + manifestPath); } var androidManifest = new AndroidManifest(manifestPath, _logger); androidManifest.RemovePreviousConfigurations(); + androidManifest.AddDisclaimerComment(); - if (!enableNativeSupport) + if (!AndroidSdkEnabled) { + // Should the SDK end up in the app regardless, this keeps it from crashing on startup. + _logger.LogDebug("Setting 'auto-init' to 'false'. The Android SDK is not part of this build."); + androidManifest.SetAutoInit(false); + _ = androidManifest.Save(); + return; } - androidManifest.AddDisclaimerComment(); - if (_options?.AndroidNativeInitializationType is NativeInitializationType.Runtime) { _logger.LogDebug("Setting 'auto-init' to 'false'. The Android SDK will be initialized at runtime."); @@ -239,7 +258,7 @@ internal void CopyAndroidSdkToGradleProject(string unityProjectPath, string grad var androidSdkPath = Path.Combine(unityProjectPath, "Packages", SentryPackageInfo.GetName(), "Plugins", "Android", "Sentry~"); var targetPath = Path.Combine(gradlePath, "unityLibrary", "libs"); - if (_options is { Enabled: true, AndroidNativeSupportEnabled: true }) + if (AndroidSdkEnabled) { if (!Directory.Exists(androidSdkPath)) { @@ -260,6 +279,13 @@ internal void CopyAndroidSdkToGradleProject(string unityProjectPath, string grad } else { + if (!Directory.Exists(androidSdkPath)) + { + // A build that does not ship the SDK has no reason to fail over a missing SDK. + _logger.LogDebug("Failed to find the Android SDK at '{0}'. Nothing to remove.", androidSdkPath); + return; + } + _logger.LogInfo("Removing the Android SDK from the output project."); foreach (var file in Directory.GetFiles(androidSdkPath)) { @@ -275,7 +301,7 @@ internal void CopyAndroidSdkToGradleProject(string unityProjectPath, string grad internal void AddAndroidSdkDependencies(string gradleProjectPath) { var tool = new GradleSetup(_logger, gradleProjectPath); - var nativeSupportEnabled = _options is { Enabled: true, AndroidNativeSupportEnabled: true }; + var nativeSupportEnabled = AndroidSdkEnabled; try { @@ -358,7 +384,7 @@ internal void SetupSymbolsUpload(string unityProjectPath, string gradleProjectPa private void SetupProguard(string gradleProjectPath) { var tool = new ProguardSetup(_logger, gradleProjectPath); - var nativeSupportEnabled = _options is { Enabled: true, AndroidNativeSupportEnabled: true }; + var nativeSupportEnabled = AndroidSdkEnabled; try { diff --git a/test/Sentry.Unity.Editor.Tests/Android/AndroidManifestConfigurationTests.cs b/test/Sentry.Unity.Editor.Tests/Android/AndroidManifestConfigurationTests.cs index 0be4840a1..fe75ebe6e 100644 --- a/test/Sentry.Unity.Editor.Tests/Android/AndroidManifestConfigurationTests.cs +++ b/test/Sentry.Unity.Editor.Tests/Android/AndroidManifestConfigurationTests.cs @@ -113,6 +113,38 @@ public void ModifyManifest_UnityOptions_EnabledWithoutDsn_LogWarningAndDoesNotAd Assert.False(manifest.Contains("io.sentry.dsn")); } + [Test] + [TestCase(null)] + [TestCase("")] + public void ModifyManifest_AndroidSdkDisabled_SetsAutoInitToFalse(string? dsn) + { + _fixture.SentryUnityOptions!.Dsn = dsn; + var sut = _fixture.GetSut(); + var manifest = WithAndroidManifest(basePath => sut.ModifyManifest(basePath)); + + StringAssert.Contains("", manifest); + } + + [Test] + public void CopyAndroidSdkToGradleProject_EnabledWithoutDsn_RemovesAndroidSdkFromGradleProject() + { + var fakeProjectPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var unityProjectPath = Path.Combine(fakeProjectPath, "UnityProject"); + var gradleProjectPath = Path.Combine(fakeProjectPath, "GradleProject"); + DebugSymbolUploadTests.SetupFakeProject(fakeProjectPath); + var androidSdk = Path.Combine(gradleProjectPath, "unityLibrary", "libs", "androidSdk.jar"); + File.Create(androidSdk).Close(); + + _fixture.SentryUnityOptions!.Dsn = string.Empty; + var sut = _fixture.GetSut(); + + sut.CopyAndroidSdkToGradleProject(unityProjectPath, gradleProjectPath); + + Assert.IsFalse(File.Exists(androidSdk)); + + Directory.Delete(fakeProjectPath, true); + } + [Test] public void ModifyManifest_UnityOptions_AndroidNativeSupportEnabledFalse_LogDebugAndDoesNotAddSentry() { @@ -514,6 +546,25 @@ public void CopyAndroidSdkToGradleProject_AndroidNativeSupportDisabledButSdkAlre Directory.Delete(fakeProjectPath, true); } + [Test] + public void CopyAndroidSdkToGradleProject_AndroidSdkDisabledAndSourceMissing_DoesNotThrow() + { + var fakeProjectPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var unityProjectPath = Path.Combine(fakeProjectPath, "UnityProject"); + var gradleProjectPath = Path.Combine(fakeProjectPath, "GradleProject"); + DebugSymbolUploadTests.SetupFakeProject(fakeProjectPath); + Directory.Delete( + Path.Combine(unityProjectPath, "Packages", SentryPackageInfo.GetName(), "Plugins", "Android", "Sentry~"), + true); + + _fixture.SentryUnityOptions!.AndroidNativeSupportEnabled = false; + var sut = _fixture.GetSut(); + + Assert.DoesNotThrow(() => sut.CopyAndroidSdkToGradleProject(unityProjectPath, gradleProjectPath)); + + Directory.Delete(fakeProjectPath, true); + } + [Test] public void CopyAndroidSdkToGradleProject_SdkAlreadyExists_OverwritesExistingSdk() {