4
votes

I have to add the Analytics tool Sentry to our Android project. In order to make it work, one needs to create mappings for the obfuscated code (from Proguard/R8) and upload it later to Sentry.

On the website https://docs.sentry.io/platforms/android/ it is even described how to do that. There it is written that one needs to create a gradle task looking like this:

gradle.projectsEvaluated {
    android.applicationVariants.each { variant ->
        def variantName = variant.name.capitalize();
        def proguardTask = project.tasks.findByName(
            "transformClassesAndResourcesWithProguardFor${variantName}")
        def dexTask = project.tasks.findByName(
            "transformClassesWithDexFor${variantName}")
        def task = project.tasks.create(
                name: "processSentryProguardFor${variantName}",
                type: Exec) {
            workingDir project.rootDir
            commandLine *[
                "sentry-cli",
                "upload-proguard",
                "--write-properties",
                "${project.rootDir.toPath()}/app/build/intermediates/assets" +
                    "/${variant.dirName}/sentry-debug-meta.properties",
                variant.getMappingFile(),
                "--no-upload"
            ]
        }
        dexTask.dependsOn task
        task.dependsOn proguardTask
    }
}

This shall wait until Proguard is finished, than copy this properties file to the assets. However, when I add this to my Android gradle script I get the error:

Could not create task ':app:processSentryProguardForPlayStoreStagingDebug'.

No signature of method: java.util.ArrayList.multiply() is applicable for argument types: (ArrayList) values: [[sentry-cli, upload-proguard, --write-properties, {Application-Path}/app/build/intermediates/assets/playStoreStaging/debug/sentry-debug-meta.properties, ...]] Possible solutions: multiply(java.lang.Number), multiply(java.lang.Number)

I assume there is something wrong with the multiplication symbol * before the commandLine array. But when I remove it I get the error

Could not create task ':app:processSentryProguardForPlayStoreStagingDebug'.

Cannot cast object 'sentry-cli' with class 'java.lang.String' to class 'int'

So I tried to test this with only that line

commandLine "sentry-cli", ...

Which gave me another error

What went wrong: Cannot invoke method dependsOn() on null object

Thus I assume something went really wrong with that gradle script since it seems the dependend task can't be found. Does anyone have any idea how to fix this (or optionally have any other idea how to copy that sentry-debug-meta.properties file to my assets in another way, once Proguard/R8 is finished)?

Thanks!

-------- EDIT --------

I noticed something important. The gradle tasks are defined in a different name than what was defined in the manual. Looking at my tasks I have them named

transformClassesAndResourcesWithR8For...

and

transformClassesWithDexBuilderFor...

However, I print the variantName then for checking but it seems my tasks are incomplete.

In my tasks list there exist

transformClassesAndResourcesWithR8ForPlayStoreStagingDebug

but not

transformClassesAndResourcesWithR8ForPlayStoreStagingRelease

and thus the task can't be found. I think that is the real problem here. So where are these gradle tasks defined?

List of my project gradle tasks

------- EDIT 2 --------

Okay I noticed something strange here. Some variants don't have tasks. It makes sense that DEBUG tasks don't have R8 tasks but I found this here:

Variant: PlayStoreStagingRelease DexTask is null

Variant: PlayStorePreviewRelease DexTask is null

Variant: HockeyAppRelease DexTask is null

Variant: LocalServerRelease DexTask is null

Variant: PlayStoreProductionRelease DexTask is null

So how can this be?

3

3 Answers

1
votes

java.util.ArrayList.multiply() hints for that * in front of the [ ] list, which looks strange to me. Try removing the *[ ], only keeping List<String> (there's no ArrayList expected, to begin with):

commandLine "sentry-cli", "upload-proguard", "--write-properties", "${project.rootDir.toPath()}/app/build/intermediates/assets/${variant.dirName}/sentry-debug-meta.properties", variant.getMappingFile(), "--no-upload"

You'd have to look up how your tasks are actually being called, but it should be something alike:

def r8Task = project.tasks.findByName("transformClassesAndResourcesWithR8For${variantName}")
def d8Task = project.tasks.findByName("transformClassesWithDexBuilderFor${variantName}")

With a null check, because not every variant might have minifyEnabled true set:

if(r8Task != null) {
    d8Task.dependsOn task
    task.dependsOn r8Task
}

Maybe even a previous null check is required, because variant.getMappingFile() needs R8.

And that some flavors have no D8 task might be based upon the absence of code (nothing to do).

2
votes

I'd recommend using the Sentry Gradle integration (Gradle plugin) which is described here https://docs.sentry.io/platforms/android/#gradle-integration

The official Android Gradle plugin changed its task names over versions, Gradle version also affects those code snippets.

Google also replaced Proguard with R8 and it also affected those code snippets.

Is there a reason why not using the Sentry Gradle integration? if so, We'll be looking into updating them.

Thanks.

0
votes

Here's a summary of the steps that I followed for integrating Sentry with my Android app. These steps are to ensure the sentry gradle plugin works as expected and automatically uploads the proguard mapping files, without you having to worry about uploading using cli. I assume you would have setup the Sentry SDK as described here: https://docs.sentry.io/platforms/android/#integrating-the-sdk

  1. Ensure you have Android Studio gradle plugin 3.5.0 (Not 3.6.x, that seems to break the sentry plugin. I observed that the sentry proguard or native symbol upload tasks are not configured or executed at all). This value should be in your root project's build.gradle in dependencies block

  2. Provide a sentry.properties file the root folder of your project. The sentry.properties file should have the following values at minimum:

    defaults.project=your_sentry_project_name
    defaults.org=your_sentry_org_name
    auth.token=sentry_project_auth_token

You can get info about generating auth tokens here: https://sentry.io/settings/account/api/auth-tokens/

  1. (Optional: If you have build flavors) In my case, I have different flavors for my app. So, I had to put the sentry.properties inside my flavor specific folder in /app/src/ folder. Then, I wrote a gradle task to copy the flavor specific sentry.properties file into the project's root folder during gradle's configuration phase. Example:
    task copySentryPropertiesTask {
        if (getBuildFlavor() != null && !getBuildFlavor().isEmpty()) {
            println("Copying Sentry properties file: ${getBuildFlavor()}")
            copy {
                from "src/${getBuildFlavor()}/"
                include "sentry.properties"
                into "../"
            }
        }
    }

    def getBuildFlavor() {
        Gradle gradle = getGradle()
        String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
        Pattern pattern;
        if (tskReqStr.contains("assemble"))
            pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
        else
            pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
        Matcher matcher = pattern.matcher(tskReqStr)
        if (matcher.find())
            return matcher.group(1)
        else {
            println "NO MATCH FOUND"
            return ""
        }
    }

Note 1: You can place this task in your app/build.gradle anywhere (I had placed it at the end).

Note 2: If you followed step 3 for build flavors, you can also add the root folder's sentry.properties in .gitignore. Since, it will be copied everytime you create a build.

Sentry should now be able to upload the proguard files for any release builds (or if you set minifyEnabled=true for any buildType).