37

Is there a reasonably simple way for a module's build.gradle file to indicate that certain files from a dependency should be excluded? I am specifically interested in excluding certain resources from an AAR.


LeakCanary is an interesting library for helping to track down memory leaks. However, it has an undocumented requirement of compileSdkVersion of 21 or higher. While most projects should not have a problem with this, it's unseemly for a library to require a certain compileSdkVersion without a good reason. A development team may have frozen their compileSdkVersion as part of a general policy to only change those sorts of settings as part of major version updates of their app or something.

In this case, for v1.3.1 of LeakCanary at least, the only reason compileSdkVersion is required, AFAICT, is because the AAR has a res/values-v21/ directory, containing a theme definition that inherits from Theme.Material. This theme is used by a diagnostic activity. That activity is never seen by end users, only by developers in debug builds. Frankly, what that activity looks like, theme-wise, does not really matter. Forcing a compileSdkVersion of 21 just to have that diagnostic activity have a certain theme is, IMHO, stupid.

It'd be nice if as part of a compile directive we could say "hey, please skip res/values-v21/ from this AAR, m'kay?". Since the -v21 theme is simply providing an alternative definition of a theme defined elsewhere, dropping the -v21 theme will not break the build or break things at runtime, but merely will give us a Holo-themed diagnostic activity.

I fail to see how this answer works with dependencies. I am also uncertain if it is complete, and it certainly does not appear to be supported. It also doesn't really qualify as "simple" — I would not expect somebody to try dropping this in a build.gradle file just to block a single file from a diagnostic library like LeakCanary.

So, is there something simpler than this that works with now-current editions of the Android Plugin for Gradle?

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • I think, the workaround for LeakCanary (https://github.com/square/leakcanary) would be to fork it and compile your own version with proper `compileSdkVersion`. I'm not sure if it can be counted as an answer to the question though. – Konstantin Loginov Dec 26 '15 at 09:19
  • @KonstantinLoginov: LeakCanary appears to have a fair number of interconnected moving parts, which is why I am skeptical that a fork will be all that easy. I did enough poking around to determine that the only Android 5.0+ feature was `Theme.Material`, which is what led me to ask this question. While I am framing the question in the context of LeakCanary, the issue transcends that one library. – CommonsWare Dec 26 '15 at 12:15
  • It would be nice if google's current gradle plugin source was public. Tags for the DSL's source [1] are way behind the release tags [2]. [1]https://android.googlesource.com/platform/tools/gradle/+refs [2]https://jcenter.bintray.com/com/android/tools/build/gradle/ – JBirdVegas Dec 30 '15 at 15:32

3 Answers3

21

EDIT:

Wrote advanced gradle task for you:

final List<String> exclusions = [];

Dependency.metaClass.exclude = { String[] currentExclusions ->
    currentExclusions.each {
        exclusions.add("${getGroup()}/${getName()}/${getVersion()}/${it}")
    }
    return thisObject
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile ('com.android.support:appcompat-v7:20.+')
    debugCompile ('com.squareup.leakcanary:leakcanary-android:1.3.1')
            .exclude("res/values-v21/values-v21.xml")
    releaseCompile ('com.squareup.leakcanary:leakcanary-android-no-op:1.3.1')
}

tasks.create("excludeTask") << {
    exclusions.each {
        File file = file("${buildDir}/intermediates/exploded-aar/${it}")
        println("Excluding file " + file)
        if (file.exists()) {
            file.delete();
        }
    }
}

tasks.whenTaskAdded({
    if (it.name.matches(/^process.*Resources$/)) {
        it.dependsOn excludeTask
    }
})

Now you can use method .exclude() on each dependency, providing into list of paths, you want to exclude from specified dependency. Also, you can stack the .exclude() method calls.

IlyaGulya
  • 957
  • 6
  • 18
  • I think your `LEAKCANARY_ARTIFACT_NOOP` definition has a bug. And while this is interesting, it does not answer the actual question: "Is there a reasonably simple way for a module's `build.gradle` file to indicate that certain files from a dependency should be excluded?" It addresses my example scenario in an alternative fashion (modifying resources). Thanks, though! – CommonsWare Dec 30 '15 at 12:49
  • I don't think that there's any way to do what you want out of the box. But you can write your own task, like this, that will be executed before resource merging and will delete resources that you don't need. All resources of each aar module are stored in build/exploded-aar directory, and you can safely remove them from there and they would not be processed at all. However, about simplicity: you can write your own wrapper for "compile" closure which can take array of files/dirs that will be excluded in task. I will try to make this tomorrow. – IlyaGulya Dec 30 '15 at 17:46
  • Edited the answer. Now excluding is much simplier, i think. – IlyaGulya Dec 30 '15 at 19:23
  • I tried using this in Android Studio 2.2.2 and it fails with: Error:Could not find method $() for arguments [build_6l34hmliq0djb0q3abscneim0$_run_closure2$_closure16$_closure17@1ee01b51] on project ':gw_lib' of type org.gradle.api.Project. – dkneller Dec 14 '16 at 15:02
  • I changed the name from "exclude" to "excludeFiles" and it works. – dkneller Dec 14 '16 at 15:20
  • 1
    This isn't a complete solution, from the perspective that if you put this in a library module it doesn't prevent the entire AAR file from being included in modules that depend on it. I'm not sure what the solution to that would be. – dkneller Dec 14 '16 at 18:48
  • 3
    Too bad it no longer works in 2020 with gradle 4.0.1 – Shark Aug 19 '20 at 15:17
5

I believe you can solve this problem more elegantly using the PackagingOptions facility of the Android Gradle Plugin DSL.

I was able to use this myself to exclude some native libraries I didn't need introduced by an AAR in my project.

android {
    ...
    packagingOptions {
        exclude '/lib/armeabi-v7a/<file_to_exclude>'
    }
}

For the case outlined in the question, I believe this would work:

android {
    ...
    packagingOptions {
        exclude '/res/values-v21/<file_to_exclude>'
    }
}
AndrewJC
  • 1,318
  • 12
  • 11
  • 3
    I'll give it a try sometime, but I am skeptical that this will work. First, `pacakgingOptions` is much too late in the build process -- the build should fail due to the `compileSdkVersion` before it gets there. Second, AFAIK, `packagingOptions` is referring to excluding things based on where they are in the APK, and XML resources do not go in the APK as ordinary files. Thanks, though! – CommonsWare Nov 10 '16 at 12:43
  • any chance to do it dynamically? https://stackoverflow.com/questions/46543271/how-to-exclude-files-from-aar-with-gradle-dynamically – 4ntoine Oct 06 '17 at 07:22
  • `packagingOptions` was what I needed to exclude specific files from the `classes.jar` of an aar package – McFarlane Oct 19 '20 at 09:06
4

Try compileOnly keyword to mark the resource is used for compile only.

dependencies {
      compileOnly fileTree(include: ['*.jar'], dir: 'libs')
}
Leo Nguyen
  • 614
  • 1
  • 9
  • 23