Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Fixes

- Fixed an Android startup crash (`DSN is required`) when initializing the native SDK at runtime alongside the Sentry Android Gradle Plugin. ([#2846](https://github.com/getsentry/sentry-unity/pull/2846))

### Dependencies

- Bump .NET SDK from v6.10.0 to v6.11.0 ([#2850](https://github.com/getsentry/sentry-unity/pull/2850))
Expand Down
101 changes: 65 additions & 36 deletions src/Sentry.Unity.Editor/Android/AndroidManifestConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public class AndroidManifestConfiguration
private readonly bool _isDevelopmentBuild;
private readonly ScriptingImplementation _scriptingImplementation;

private bool? _androidSdkEnabled;

public AndroidManifestConfiguration()
: this(
SentryScriptableObject.LoadOptions,
Expand Down Expand Up @@ -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);
}
/// <summary>
/// Whether this build ships the Android SDK. Shared with <see cref="ModifyManifest"/> because an app that
/// carries `sentry-android-core` without the matching manifest entries auto-initializes without a DSN,
/// which crashes it on startup.
/// </summary>
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.");
Expand Down Expand Up @@ -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))
{
Expand All @@ -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))
{
Expand All @@ -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
{
Comment thread
sentry[bot] marked this conversation as resolved.
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -470,7 +496,7 @@ public void AddDisclaimerComment() =>
_applicationElement.AppendChild(_applicationElement.OwnerDocument.CreateComment(Disclaimer));

internal void SetAutoInit(bool enableAutoInit)
=> SetMetaData($"{SentryPrefix}.auto-init", enableAutoInit.ToString());
=> SetMetaData($"{SentryPrefix}.auto-init", enableAutoInit);

internal void SetDsn(string dsn) => SetMetaData($"{SentryPrefix}.dsn", dsn);

Expand All @@ -482,58 +508,58 @@ internal void SetSampleRate(float sampleRate) =>

internal void SetDist(string dist) => SetMetaData($"{SentryPrefix}.dist", dist);

internal void SetAttachScreenshot(bool value) => SetMetaData($"{SentryPrefix}.attach-screenshot", value.ToString());
internal void SetAttachScreenshot(bool value) => SetMetaData($"{SentryPrefix}.attach-screenshot", value);

internal void SetEnvironment(string environment) => SetMetaData($"{SentryPrefix}.environment", environment);

internal void SetSDK(string name) => SetMetaData($"{SentryPrefix}.sdk.name", name);

internal void SetAutoSessionTracking(bool enableAutoSessionTracking)
=> SetMetaData($"{SentryPrefix}.auto-session-tracking.enable", enableAutoSessionTracking.ToString());
=> SetMetaData($"{SentryPrefix}.auto-session-tracking.enable", enableAutoSessionTracking);

public void SetAutoAppLifecycleBreadcrumbs(bool enableAutoAppLifeCycleBreadcrumbs)
=> SetMetaData($"{SentryPrefix}.breadcrumbs.app-lifecycle", enableAutoAppLifeCycleBreadcrumbs.ToString());
=> SetMetaData($"{SentryPrefix}.breadcrumbs.app-lifecycle", enableAutoAppLifeCycleBreadcrumbs);

internal void SetAnr(bool enableAnr)
=> SetMetaData($"{SentryPrefix}.anr.enable", enableAnr.ToString());
=> SetMetaData($"{SentryPrefix}.anr.enable", enableAnr);

internal void SetPersistentScopeObserver(bool enableScopePersistence)
=> SetMetaData($"{SentryPrefix}.enable-scope-persistence", enableScopePersistence.ToString());
=> SetMetaData($"{SentryPrefix}.enable-scope-persistence", enableScopePersistence);

internal void SetAttachAnrThreadDump(bool attachAnrThreadDump)
=> SetMetaData($"{SentryPrefix}.anr.attach-thread-dumps", attachAnrThreadDump.ToString());
=> SetMetaData($"{SentryPrefix}.anr.attach-thread-dumps", attachAnrThreadDump);

internal void SetNdkAppHangTracking(bool enableNdkAppHangTracking)
=> SetMetaData($"{SentryPrefix}.ndk.app-hang.enable", enableNdkAppHangTracking.ToString());
=> SetMetaData($"{SentryPrefix}.ndk.app-hang.enable", enableNdkAppHangTracking);

internal void SetNdkAppHangTimeout(long ndkAppHangTimeout)
=> SetMetaData($"{SentryPrefix}.ndk.app-hang.timeout-interval-millis", ndkAppHangTimeout.ToString(CultureInfo.InvariantCulture));

internal void SetTombstone(bool enableTombstone)
=> SetMetaData($"{SentryPrefix}.tombstone.enable", enableTombstone.ToString());
=> SetMetaData($"{SentryPrefix}.tombstone.enable", enableTombstone);

internal void SetTombstoneReportHistorical(bool enableTombstoneReportHistorical)
=> SetMetaData($"{SentryPrefix}.tombstone.report-historical", enableTombstoneReportHistorical.ToString());
=> SetMetaData($"{SentryPrefix}.tombstone.report-historical", enableTombstoneReportHistorical);

internal void SetNdkEnabled(bool enableNdk)
=> SetMetaData($"{SentryPrefix}.ndk.enable", enableNdk.ToString());
=> SetMetaData($"{SentryPrefix}.ndk.enable", enableNdk);

internal void SetNdkScopeSync(bool enableNdkScopeSync)
=> SetMetaData($"{SentryPrefix}.ndk.scope-sync.enable", enableNdkScopeSync.ToString());
=> SetMetaData($"{SentryPrefix}.ndk.scope-sync.enable", enableNdkScopeSync);

internal void SetNdkSdkName(string sdkName)
=> SetMetaData($"{SentryPrefix}.ndk.sdk-name", sdkName);

internal void SetAutoTraceIdGeneration(bool enableAutoTraceIdGeneration)
=> SetMetaData($"{SentryPrefix}.traces.enable-auto-id-generation", enableAutoTraceIdGeneration.ToString());
=> SetMetaData($"{SentryPrefix}.traces.enable-auto-id-generation", enableAutoTraceIdGeneration);

internal void SetEnableUserInteractionBreadcrumbs(bool enableUserInteractionBreadcrumbs)
=> SetMetaData($"{SentryPrefix}.breadcrumbs.user-interaction", enableUserInteractionBreadcrumbs.ToString());
=> SetMetaData($"{SentryPrefix}.breadcrumbs.user-interaction", enableUserInteractionBreadcrumbs);

internal void SetEnableUserInteractionTracing(bool enableUserInteractionTracing)
=> SetMetaData($"{SentryPrefix}.traces.user-interaction.enable", enableUserInteractionTracing.ToString());
=> SetMetaData($"{SentryPrefix}.traces.user-interaction.enable", enableUserInteractionTracing);

internal void SetDebug(bool debug) => SetMetaData($"{SentryPrefix}.debug", debug ? "true" : "false");
internal void SetDebug(bool debug) => SetMetaData($"{SentryPrefix}.debug", debug);

// https://github.com/getsentry/sentry-java/blob/db4dfc92f202b1cefc48d019fdabe24d487db923/sentry/src/main/java/io/sentry/SentryLevel.java#L4-L9
internal void SetLevel(SentryLevel level) =>
Expand All @@ -547,6 +573,9 @@ internal void SetLevel(SentryLevel level) =>
_ => "debug"
});

// The Sentry Gradle plugin only recognizes lowercase booleans when resolving the metadata at build time.
Comment on lines 573 to +576

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The check in SetupSymbolsUpload is outdated and doesn't validate the DSN, causing symbol uploads to be configured even when the native SDK is not included.
Severity: MEDIUM

Suggested Fix

Update the conditional check in the SetupSymbolsUpload method to use AndroidSdkEnabled. This property correctly validates that a DSN is present by calling _options.IsValid(), aligning its behavior with other methods like AddAndroidSdkDependencies and ensuring symbol uploads are only configured when the native SDK is properly enabled.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/Sentry.Unity.Editor/Android/AndroidManifestConfiguration.cs#L573-L576

Potential issue: The `SetupSymbolsUpload` method contains an outdated conditional check,
`_options is not { Enabled: true, AndroidNativeSupportEnabled: true }`. Unlike other
related methods that were updated to use `AndroidSdkEnabled` (which validates the DSN
via `_options.IsValid()`), this check does not validate the DSN. As a result, if
`Enabled` and `AndroidNativeSupportEnabled` are true but the DSN is empty, the symbol
upload task will be configured even though the native SDK dependencies are not included
in the build. This creates an inconsistency introduced by the new changes.

private void SetMetaData(string key, bool value) => SetMetaData(key, value ? "true" : "false");

private void SetMetaData(string key, string value)
{
var element = _applicationElement.AppendChild(_applicationElement.OwnerDocument
Expand Down
Loading
Loading