getsentry / sentry-android-gradle-plugin

Gradle plugin for Sentry Android. Upload proguard, debug files, and more.
https://docs.sentry.io/platforms/android/gradle/
MIT License
141 stars 32 forks source link

Support proguard mapping upload for any JVM app #403

Open romtsn opened 1 year ago

romtsn commented 1 year ago

Description

Since compose-desktop receives more and more traction, and people usually like to minify their apps (to reduce the final .jar size, and also to optimize their bytecode), we should look into how to provide automatic support for uploading proguard/R8 mapping files.

Since there's no standardized way of using proguard/r8 with pure JVM apps, I guess we'll need to provide some configuration option, which task to depend on or where is the location of the mapping file.

This is also valid for any JVM desktop app (JavaFX, Swing, etc.) that uses a minification tool.

alexstyl commented 1 year ago

Thanks for opening this issue @romtsn

For future reference, you can specify where you want the mapping file to be stored by passing a custom proguard configuration like this:

// desktop/build.gradle.kts

compose.desktop {
        // your app's configuration 

        buildTypes.release.proguard {
            obfuscate.set(true)
            configurationFiles.from(project.file("compose-desktop.pro"))
        }
    }
}

and in your compose-desktop.pro file. use the -printmapping option (see the doc):

-printmapping mapping.txt

# your other proguard configurations & rules
leonard84 commented 1 year ago

(Moved from #429)

Problem Statement

Currently, sentry only has a Gradle plugin for Android builds that integrates tightly into AGP.

We are developing Java applications, and want to use the release announcement, the proguard mapping file upload, and source map upload functionality in a regular java Gradle build.

Solution Brainstorm

Extract common functionality from the sentry-android-gradle-plugin into a shared component and create a sentry-gradle-plugin that offers tasks to announce a release as well as upload proguard mapping files. These tasks should be able to accept all the required and optional parameters of the CLI commands. It should also register an extension to configure common parameters across the default tasks, such as the auth token.

romtsn commented 1 year ago

@leonard84 thanks for moving it here. Could you please give more details about how you use proguard/r8? Is it through a custom task or through a gradle plugin (if so, which one)? Just wanna know better which tasks do we have to hook with. Thanks

leonard84 commented 1 year ago

It is a custom subclass of the official proguard.gradle.ProGuardTask to fix some issues with build caching that the original has.

Nohus commented 9 months ago

While I appreciate that having this upload functionality in a Gradle plugin could be useful for someone, why can't we just upload the mapping files on the web interface?

My expectation was that when I open a Sentry issue with an obfuscated stracktrace, Sentry would show a prompt like "we don't have a mapping file for version 1.2.3, click to upload", and let me upload my mapping-1.2.3.txt right then.

I don't even use the Gradle plugin in my project and don't see a need to complicate my build.

adinauer commented 9 months ago

@Nohus thanks for the feedback.

You could try to manually upload your mapping files to Sentry using CI. We have sentry-cli which allows you to upload. Here's how we use it to upload mapping files from the Gradle plugin. There's some docs available as well.

You'd also have to specify io.sentry.ProguardUuids or use setProguardUuid so events sent to Sentry know which mapping file should be used. Both the upload and running applications need to know the UUID of the mapping file.

As for doing this after the fact. That's currenlty not possible as events are processed as they come in so adding the mapping file after events arrived won't change the events as it is implemented now. There were discussions around doing source lookup when looking at events in the Sentry UI instead of doing it during processing but as far as I know nothing has been implemented yet and there's no ETA for this change. Applying mapping files could work the same way but since you'd have to keep versioned mapping files around then manually upload them and add the UUID to the running application anyways, I'd suggest setting up CI to do the upload for you using sentry-cli as suggested above.

Nohus commented 9 months ago

Thank you for the information and links, I have used sentry-cli to upload the mapping file from my CI and it is working correctly now.

sproctor commented 8 months ago

@Nohus Can you share how you did this? I'm working on the same thing currently.

Nohus commented 8 months ago

@sproctor This is the gist of it:

# Create release on Sentry
sentry-cli releases new \
    --project "app" \
    --finalize "app@$version"

# Upload ProGuard mapping
sentry-cli upload-proguard \
    --uuid "$buildUuid" \
    "build/mapping-$version.txt" \
    --require-one \
    --project "app" \
    --app-id "app" \
    --platform "kotlin" \
    --version "$version" \
sproctor commented 7 months ago

This was about half of it. I'll add my solution here for anyone else interested. I had a terrible time getting a properties file generated. It worked fine locally but not on CI.

Gradle task:

abstract class SentryGenerateProguardUuidTask : DefaultTask() {

    init {
        outputs.upToDateWhen { false }
    }

    @get:Input
    abstract val output: Property<File>

    @TaskAction
    fun createUuidFile() {
        val outputFile = output.get()
        val uuid = UUID.randomUUID()
        val props = Properties().also {
            it.setProperty("io.sentry.ProguardUuids", uuid.toString())
        }
        outputFile.writer().use { writer ->
            props.store(writer, "")
        }
        logger.info("SentryGenerateProguardUuidTask - outputFile: ${outputFile.absolutePath}, uuid: $uuid")
    }
}

tasks {
    val sentryUuid = register<SentryGenerateProguardUuidTask>("sentryUuid") {
        dependsOn("processResources")
        output.set(layout.buildDirectory.file("resources/main/sentry_proguard_uuid.properties").get().asFile)
    }

    compileKotlin {
        dependsOn(sentryUuid)
    }
}

Github action:

...
      - name: Setup Sentry CLI
        run: npm install @sentry/cli

      - name: Upload mapping
        shell: bash
        run: |
          SENTRY=node_modules/.bin/sentry-cli
          VERSION=${GITHUB_REF_NAME:1}
          while IFS='=' read -r key value
          do
            key=$(echo $key | tr '.' '_')
            value=$(echo $value | tr -d '\n\r')
            eval ${key}=\${value}
          done < app/build/resources/main/sentry_proguard_uuid.properties
          $SENTRY releases new --project "app" --org "org" --finalize "desktop@$VERSION" --auth-token "${{ secrets.SENTRY_AUTH_TOKEN }}"
          $SENTRY upload-proguard --uuid "$io_sentry_ProguardUuids" "app/build/mapping.txt" --require-one --project "app" --org "org" --app-id "desktop" --platform "kotlin" --version "$VERSION" --auth-token "${{ secrets.SENTRY_AUTH_TOKEN }}"
adinauer commented 7 months ago

Thanks for sharing