Adding support for generating or passing UUID when uploading progurad mapping on .NET MAUI
Problem Statement
Uploading a progurad file by <SentryUploadAndroidProguardMapping> but this file is not associated with any release
According https://docs.sentry.io/cli/dif/#uploading-files this association requires adding io.sentry.proguard-uuid meetadata to and using sentry-cli upload-proguard --uuid ...
Solution Brainstorm
Preferred solution: Generate UUID, inject it to AndroidManifest.xml and use during uploading
or: read this UUID from AndroidManifest.xml and use during uploading
or: add support for an environment variable
BTW: Shouldn't other switches --app-id my.app.id, --version. --version-code be used as well?
Checks out, without uuid the there's no way for the mapping file and the app release to be associated.
Here's we add it to the gradle plugin:
https://github.com/getsentry/sentry-android-gradle-plugin/blob/3400d0b301a068815e73d240391043768a1c32ed/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt#L67-L68
The app also needs to know what the uuid was, so it can attach it to events. The server needs to know what mapping file to look up.
like: https://github.com/getsentry/sentry-android-gradle-plugin/blob/3400d0b301a068815e73d240391043768a1c32ed/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt#L54C19-L54C56
Ideally we use meta-data, like the docs you linked say:
<application>
<meta-data
android:name="io.sentry.proguard-uuid"
android:value="A_VALID_UUID"
/>
</application>
I managed to find a workaround until this bug will be fixed:
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<ProguardUuid>$([System.Guid]::NewGuid())</ProguardUuid>
</PropertyGroup>
<ItemGroup Condition="'$(Configuration)'=='Release'">
<AssemblyAttribute Include="Android.App.MetaData">
<_Parameter1>"io.sentry.proguard-uuid", Value = "$(ProguardUuid)"</_Parameter1>
<_Parameter1_IsLiteral>true</_Parameter1_IsLiteral>
</AssemblyAttribute>
</ItemGroup>
<!-- Upload Android Proguard mapping file to Sentry after the build. -->
<Target Name="CustomUploadAndroidProguardMappingFileToSentry" AfterTargets="UploadDebugInfoToSentry" DependsOnTargets="PrepareSentryCLI"
Condition="'$(SentryCLI)' != '' And '$(AndroidProguardMappingFile)' != '' And '$(Configuration)'=='Release'">
<Message Importance="High" Text="Preparing to upload Android Proguard mapping to Sentry for '$(MSBuildProjectName)': $(AndroidProguardMappingFile))" />
<Exec Command="$(SentryCLIProGuardMappingUploadCommand) --uuid "$(ProguardUuid)" --app-id "$(ApplicationId)" --version "$(ApplicationDisplayVersion)" --version-code "$(ApplicationVersion)" $(AndroidProguardMappingFile)" IgnoreExitCode="true" ContinueOnError="WarnAndContinue">
<Output TaskParameter="ExitCode" PropertyName="_SentryCLIExitCode" />
</Exec>
<Warning Condition="'$(_SentryCLIExitCode)' != '0'" Text="Sentry CLI could not upload proguard mapping." />
</Target>
please note than I use separated platform project and these entries I added only to my Android projects. For single project MAUI app some extra conditions may be required
Thank you for sharing that! would you be willing to share that fix in a PR? We have a single file where we do the sentry cli commands. it just missed these its u figured out already.
Sure! Feel free to use it.
Thanks for the code @rafalka I've made my own version of your code, digging into how things work and understanding it properly.
If using VS I highly recommend MSBuild Editor extension to effectively navigate and dig into any .targets / .props files. I use it to discover where MSBuild variables are defined, and places they are consumed.
Notes about the code
- in theory
SentryCLIUploadOptionsshould have been enough, not needing theTargetcode. But inSentry.targetsit is applied to bothSentryCLIProGuardMappingUploadCommandandSentryCLIDebugFilesUploadCommand - the definition of
SentryCLIUploadOptions2is my thing, only applying it toSentryCLIProGuardMappingUploadCommand - I'm unsure parameters
--app-id/--version/--version-codeare really needed inSentryCLIProGuardMappingUploadCommand
The code comments describe how things are tied together from build to embedding in app, and finally adding the UUID to events sent to Sentry.
<!-- ISSUE: Adding support for generating or passing UUID when uploading progurad mapping on .NET MAUI -->
<!-- https://github.com/getsentry/sentry-dotnet/issues/3872 -->
<PropertyGroup Condition="'$(Configuration)'=='Release' AND $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<ProguardUuid>$([System.Guid]::NewGuid())</ProguardUuid>
<SentryCLIUploadOptions2>--uuid "$(ProguardUuid)" --app-id "$(ApplicationId)" --version "$(ApplicationDisplayVersion)" --version-code "$(ApplicationVersion)"</SentryCLIUploadOptions2>
</PropertyGroup>
<ItemGroup Condition="'$(Configuration)'=='Release' AND $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<!-- [MSBuild] write .NET assembly attribute Android.App.MetaDataAttribute -->
<!-- https://learn.microsoft.com/en-us/dotnet/api/android.app.metadataattribute -->
<!-- [MSBuild] which will be copied to Android manifest -->
<!-- https://github.com/dotnet/android/blob/e6e83daebe7e7d0126329a49ac3c615914eeed59/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs#L587 -->
<!-- https://learn.microsoft.com/en-us/dotnet/maui/android/manifest -->
<!-- [Sentry Java SDK] manifest read by Sentry and added to events sent by app -->
<!-- https://github.com/getsentry/sentry-java/blob/8696c895261a3f15fd8146f5829851cc701530a5/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java#L78 -->
<!-- NOTE: as of Sentry 5.7.0 (NuGet) proguard UUID are not added to SentryExent debug_meta/images -->
<!-- https://develop.sentry.dev/sdk/data-model/event-payloads/debugmeta/#proguard-images -->
<AssemblyAttribute Include="Android.App.MetaData">
<_Parameter1>"io.sentry.proguard-uuid", Value = "$(ProguardUuid)"</_Parameter1>
<_Parameter1_IsLiteral>true</_Parameter1_IsLiteral>
</AssemblyAttribute>
</ItemGroup>
<Target Name="AppendSentryCLIProGuardMappingUploadCommand" BeforeTargets="UploadAndroidProguardMappingFileToSentry" DependsOnTargets="PrepareSentryCLI">
<PropertyGroup>
<SentryCLIProGuardMappingUploadCommand Condition="'$(SentryCLIUploadOptions2.Trim())' != ''">$(SentryCLIProGuardMappingUploadCommand) $(SentryCLIUploadOptions2.Trim())</SentryCLIProGuardMappingUploadCommand>
</PropertyGroup>
</Target>
With the above solution I get the build time generated UUID embedded into the Android manifest, as well as associated with proguard mappings uploaded to Sentry. All good.
I tested it using Sentry.Maui v5.7.0 and discovered that it partially works. Events stemming from the native SDK contains the UUID. But not the ones stemming from the .NET side of things.
I can't make a workaround because the .NET definition of DebugImage for debug_meta.images doesn't have a property for uuid. And the class is also sealed.
An event should look like this
I didn't dig into the details yet so only commenting on:
I can't make a workaround because the .NET definition of DebugImage for debug_meta.images doesn't have a property for uuid. And the class is also sealed.
we can add properties if that's needed btw, but not sure if this is really needed since the Android SDK under the hood should take care of adding proguard information to events with ninified Java code. But from C# is this needed?
Also note that looking at json events after processing (downloaded from sentry) they will contain more metadata which gets added while processing the event
I tested it using Sentry.Maui v5.7.0 and discovered that it partially works. Events stemming from the native SDK contains the UUID. But not the ones stemming from the .NET side of things.
Is this needed in C# events? there's no minified Java in those, are there? if so, I'm not even sure just adding the IDs will fix it. Some examples/reports would be helpful to look into
Honestly I have no idea if the proguard mappings come into play with regards to .NET code. I was assuming AOT assemblies was processed. But it is perhaps, as you mentioned, restricted to Java bytecode.
Another thought. What about calls into Java libraries by the .NET runtime - having stacktraces that contains both Java and .NET functions. How can those stacktraces be symbolicated correctly without the proguard mappings? Food for thought. I'll have to dig deeper myself.
Another thought. What about calls into Java libraries by the .NET runtime - having stacktraces that contains both Java and .NET functions. How can those stacktraces be symbolicated correctly without the proguard mappings? Food for thought. I'll have to dig deeper myself.
If we're calling these via JNI, I believe proguard can't be used on those members since their name is written on the .NET side and if they got renamed by proguard, code would fail at runtime.
So I believe code that gets affected by proguard is in practice not accessed via C#. I could be wrong and proguard somehow runs and the output of the mappings is used by .NET to update its JNI calls and call into the new/minified code. But I'm unaware if that's the case.
I'd expect in such case for us to get some reports of stack traces in Sentry showing minified Java code (Perhaps interleaved with C# code) but I haven't seen such report yet.
All of that said, having the proguard uuid in the manifest (or sentry.properties) is still needed so that any Java error that happens to run minified code gets properly unminified/symbolicated. So does make sense to make sure we get the uuid added to the manifest metadata.