iTwenty
iTwenty

Reputation: 973

Ignore proguard configuration of an external library

So, I want to add an external library to my project. The library itself is quite small, around 300 methods. But it is configured to be very liberal with it's proguard configuration. I ran a simple test with/without the library and with/without proguard on a barebones project and this is what I came up with

Proguard    Lib     Method Count
N           N       15631
Y           N       6370
N           Y       15945
Y           Y       15573

As you can see, with proguard enabled, the count is ~6000. But the moment I add the lib, count shoots up to ~15000 despite the library itself being only ~300 methods.

So my question is, how do I ignore the proguard configuration of this particular library?

UPDATE:

It is not possible with android gradle plugin now. I found android bug which doesn't have priority at all. Please avoid answers with mentioning "it is not possible" and keep question opened until a workaround or an official decision is possible. Otherwise, you will collect half of bounty without adding value. Thanks!

Upvotes: 18

Views: 4612

Answers (3)

Thomas W.
Thomas W.

Reputation: 339

Edit: In Gradle 7.3 there is a method where you can specify for which dependencies to ignore keep rules: https://developer.android.com/reference/tools/gradle-api/7.3/com/android/build/api/dsl/KeepRules#ignoreExternalDependencies(kotlin.Array)

For example:

buildTypes {
    release {
        optimization.keepRules {
            it.ignoreExternalDependencies("androidx.lifecycle:lifecycle-runtime")
        }
    }
}

Old answer: Inspired by Jonas' answer, modified for Kotlin DSL and confirmed working on Android Gradle plugin 7.2.1:

import com.android.build.gradle.internal.tasks.ProguardConfigurableTask

afterEvaluate {
    // Get each ProguardConfigurableTask
    tasks.withType(ProguardConfigurableTask::class.java).forEach { task ->
        // Remove proguard rules from lifecycle-runtime library
        val filteredConfigurationFiles = task.configurationFiles.filter { file ->
            !file.path.contains("lifecycle-runtime")
        }
        task.configurationFiles.setFrom(filteredConfigurationFiles)
    }
}

Upvotes: 3

Jonas Masalskis
Jonas Masalskis

Reputation: 1256

In case you're using R8 (which replaced ProGuard since Android Gradle plugin 3.4.0) - you can filter-out specific consumer rule files by adding the following work-around to your module's build.gradle:

tasks.whenTaskAdded { Task task ->
    // Once 'minifyEnabled' is set to 'true' for a certain build type/variant - 
    // a 'minify<variantName>WithR8' task will be created for each such variant
    //
    // - This task is implemented by com.android.build.gradle.internal.tasks.R8Task
    // - R8Task extends from ProguardConfigurableTask
    // - ProguardConfigurableTask exposes property 'configurationFiles'
    // - configurationFiles contains all files that will be contributing R8 rules
    // - configurationFiles is mutable (its type is ConfigurableFileCollection)
    //
    // Thus - we can overwrite the list of files and filter them out as we please
    //
    // More details: https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/R8Task.kt
    if (task.name.startsWith("minify") && task.name.endsWith("WithR8")) {
        afterEvaluate {
            def filteredList = task.configurationFiles.filter {
                // Example paths in this collection:
                // /Users/me/MyProject/myModule/proguard-rules.pro
                // (for library dependencies) /Users/me/.gradle/caches/<...>/okhttp3.pro

                // The below filter condition will, for example, exclude consumer ProGuard rules file from the AndroidX RecyclerView library
                !it.path.contains("recyclerview-1.1.0")
            }
            task.configurationFiles.setFrom(filteredList.files)
        }
    }
}

The above work-around was confirmed to be working on Android Gradle plugin 4.2.2

If one decides to rely on such a work-around - it might be a good idea to also add some sort of automated checks and/or tests to make sure this filtering is working. Given that the solution is quite fragile and can break with future updates of Android Gradle plugin.

Upvotes: 3

T. Neidhart
T. Neidhart

Reputation: 6200

In this specific case you have a few options:

  • extract the classes.jar file from the aar and include it as normal jar dependency in your project (will not work when the aar includes resources)
  • change the aar and remove the consumer proguard rules from it
  • use DexGuard which allows you to filter out unwanted consumer rules
  • do a bit of gradle hacking, see below

Add the following to your build.gradle:

afterEvaluate {
  // All proguard tasks shall depend on our filter task
  def proguardTasks = tasks.findAll { task ->
    task.name.startsWith('transformClassesAndResourcesWithProguardFor') }
  proguardTasks.each { task -> task.dependsOn filterConsumerRules }
}

// Let's define our custom task that filters some unwanted
// consumer proguard rules
task(filterConsumerRules) << {
  // Collect all consumer rules first
  FileTree allConsumerRules = fileTree(dir: 'build/intermediates/exploded-aar',
                                       include: '**/proguard.txt')

  // Now filter the ones we want to exclude:
  // Change it to fit your needs, replace library with
  // the name of the aar you want to filter.
  FileTree excludeRules = allConsumerRules.matching {
    include '**/library/**'
  }

  // Print some info and delete the file, so ProGuard
  // does not pick it up. We could also just rename it.
  excludeRules.each { File file ->
    println 'Deleting ProGuard consumer rule ' + file
    file.delete()
  }
}

When using DexGuard (7.2.02+), you can add the following snippet to your build.gradle:

dexguard {
  // Replace library with the name of the aar you want to filter
  // The ** at the end will include every other rule.
  consumerRuleFilter '!**/library/**,**'
}

Mind that the logic is inverted to the ProGuard example above, the consumerRuleFilter will only include consumer rules that match the pattern.

Upvotes: 11

Related Questions