Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android X + Truth + Guava test compile issue

I have an Android library (called api) gradle module as part of a larger project. I just migrated the whole project to AndroidX. I now have this error when running instrumentation test on the api lib:

 Task :api:checkDebugAndroidTestDuplicateClasses FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':api:checkDebugAndroidTestDuplicateClasses'.
> 1 exception was raised by workers:
  java.lang.RuntimeException: java.lang.RuntimeException: Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules jetified-guava-25.1-android.jar (com.google.guava:guava:25.1-android) and listenablefuture-1.0.jar (com.google.guava:listenablefuture:1.0)

If I check the runtime class path for the debugAndroidTest variant:

./gradlew api:dependencies --configuration debugAndroidTestRuntimeClasspath | grep --color -E "guava|$"

I get this output. I can see the problem:

------------------------------------------------------------
Project :api
------------------------------------------------------------
debugAndroidTestRuntimeClasspath - Resolved configuration for runtime for variant: debugAndroidTest
+--- project :test_utils
|    +--- project :core
...
|    +--- project :api (*)
|    +--- com.google.android.material:material:1.1.0-alpha03
|    |    +--- androidx.annotation:annotation:1.0.1 -> 1.1.0-alpha01
|    |    +--- androidx.appcompat:appcompat:1.1.0-alpha01
|    |    |    +--- androidx.annotation:annotation:1.0.0 -> 1.1.0-alpha01
|    |    |    +--- androidx.core:core:1.1.0-alpha01 -> 1.1.0-alpha03
|    |    |    |    +--- com.google.guava:listenablefuture:1.0 // <------ GUAVA
|    |    |    |    +--- androidx.annotation:annotation:1.0.1 -> 1.1.0-alpha01
...
+--- com.google.truth:truth:0.42
|    +--- com.google.guava:guava:25.1-android / <------ MORE GUAVA
|    |    +--- com.google.code.findbugs:jsr305:3.0.2
|    |    +--- com.google.errorprone:error_prone_annotations:2.1.3 -> 2.3.1
|    |    +--- com.google.j2objc:j2objc-annotations:1.1
|    |    \--- org.codehaus.mojo:animal-sniffer-annotations:1.14
|    +--- org.checkerframework:checker-compat-qual:2.5.3
|    +--- org.checkerframework:checker-qual:2.5.3
|    +--- junit:junit:4.12 (*)
|    +--- com.googlecode.java-diff-utils:diffutils:1.3.0
|    +--- com.google.auto.value:auto-value-annotations:1.6.2
|    \--- com.google.errorprone:error_prone_annotations:2.3.1
...

AndroidX core is depending on the new "ListableFuture-only" build of guava and Truth is depending on the full Guava 25.

I think I understand the underlying problem with ListenableFuture: https://groups.google.com/forum/#!topic/guava-announce/Km82fZG68Sw

What is the right solution here?

I don't want to exclude Guava from Truth entirely (otherwise Truth won't compile)

androidTestImplementation("com.google.truth:truth:0.42") {
    exclude group: 'com.google.guava', module: 'guava'
}

Can I exclude + force update to Guava 27 by making it a first-level dependency:

androidTestImplementation("com.google.truth:truth:$rootProject.ext.truthVersion") {
    exclude group: 'com.google.guava', module: 'guava'
}
// must add guava as top level dependency to force Truth to use latest version
androidTestImplementation 'com.google.guava:guava:27.0.1-android'

If I do this, should I be using the android or JRE version of guava?

Side question:

Why don't I see the guava dependency when looking at compile classpath? The error is a compile time error, not a runtime error

./gradlew api:dependencies --configuration debugAndroidTestCompileClasspath | grep --color -E "guava|$"

Resulting deps:

debugAndroidTestCompileClasspath - Resolved configuration for compilation for variant: debugAndroidTest
+--- project :test_utils // <----------- why are test_utils deps not listed here???
...
+--- com.google.truth:truth:0.42
|    +--- com.google.guava:guava:25.1-android <------ GUAVA
|    |    +--- com.google.code.findbugs:jsr305:3.0.2
|    |    +--- org.checkerframework:checker-compat-qual:2.0.0 -> 2.5.3
|    |    +--- com.google.errorprone:error_prone_annotations:2.1.3 -> 2.3.1
|    |    +--- com.google.j2objc:j2objc-annotations:1.1
|    |    \--- org.codehaus.mojo:animal-sniffer-annotations:1.14
|    +--- org.checkerframework:checker-compat-qual:2.5.3
|    +--- org.checkerframework:checker-qual:2.5.3
|    +--- junit:junit:4.12 (*)
|    +--- com.googlecode.java-diff-utils:diffutils:1.3.0
|    +--- com.google.auto.value:auto-value-annotations:1.6.2
|    \--- com.google.errorprone:error_prone_annotations:2.3.1
...
+--- com.google.truth:truth:{strictly 0.42} -> 0.42 (c)
+--- com.google.guava:guava:{strictly 25.1-android} -> 25.1-android (c) // <--------- why is this listed again here at top level?

Update:

After updating to Truth 0.43 I'm now seeing this error:

> Could not resolve all artifacts for configuration ':mymodule:debugAndroidTestRuntimeClasspath'.
   > Could not resolve com.google.guava:listenablefuture:{strictly 1.0}.
     Required by:
         project :mymodule
      > Cannot find a version of 'com.google.guava:listenablefuture' that satisfies the version constraints:
           Dependency path 'example:mymodule:unspecified' --> 'com.google.truth:truth:0.43' --> 'com.google.guava:guava:27.0.1-android' --> 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
           Constraint path 'example:mymodule:unspecified' --> 'com.google.guava:listenablefuture:{strictly 1.0}' because of the following reason: debugRuntimeClasspath uses version 1.0
           Dependency path 'example:mymodule:unspecified' --> 'example:myothermodule:unspecified' --> 'andrexampleoidx.core:core:1.1.0-alpha04' --> 'com.google.guava:listenablefuture:1.0'
           Constraint path 'example:mymodule:unspecified' --> 'com.google.guava:listenablefuture:{strictly 1.0}' because of the following reason: debugRuntimeClasspath uses version 1.0
            ...
           Dependency path 'example:mymodule:unspecified' --> 'example:myothermodule:unspecified' --> 'androidx.core:core:1.1.0-alpha04' --> 'androidx.concurrent:concurrent-futures:1.0.0-alpha02' --> 'com.google.guava:listenablefuture:1.0'
           Constraint path 'example:mymodule:unspecified' --> 'com.google.guava:listenablefuture:{strictly 1.0}' because of the following reason: debugRuntimeClasspath uses version 1.0
           ...

   > Could not resolve com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava.
     Required by:
         project :mymodule > com.google.truth:truth:0.43 > com.google.guava:guava:27.0.1-android
      > Cannot find a version of 'com.google.guava:listenablefuture' that satisfies the version constraints:
           Dependency path 'example:mymodule:unspecified' --> 'com.google.truth:truth:0.43' --> 'com.google.guava:guava:27.0.1-android' --> 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
           Constraint path 'example:mymodule:unspecified' --> 'com.google.guava:listenablefuture:{strictly 1.0}' because of the following reason: debugRuntimeClasspath uses version 1.0
           Dependency path 'example:mymodule:unspecified' --> 'example:myothermodule:unspecified' --> 'androidx.core:core:1.1.0-alpha04' --> 'com.google.guava:listenablefuture:1.0'
           ...
           Dependency path 'example:mymodule:unspecified' --> 'example:myothermodule:unspecified' --> 'androidx.core:core:1.1.0-alpha04' --> 'androidx.concurrent:concurrent-futures:1.0.0-alpha02' --> 'com.google.guava:listenablefuture:1.0'
           Constraint path 'example:mymodule:unspecified' --> 'com.google.guava:listenablefuture:{strictly 1.0}' because of the following reason: debugRuntimeClasspath uses version 1.0
           ...

   > Could not resolve com.google.guava:listenablefuture:1.0.
     Required by:
         project :mymodule > androidx.core:core:1.1.0-alpha04
         project :mymodule > androidx.concurrent:concurrent-futures:1.0.0-alpha02
      > Cannot find a version of 'com.google.guava:listenablefuture' that satisfies the version constraints:
           Dependency path 'example:mymodule:unspecified' --> 'com.google.truth:truth:0.43' --> 'com.google.guava:guava:27.0.1-android' --> 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
           Constraint path 'example:mymodule:unspecified' --> 'com.google.guava:listenablefuture:{strictly 1.0}' because of the following reason: debugRuntimeClasspath uses version 1.0
           ...
           Dependency path 'example:mymodule:unspecified' --> 'example:myothermodule:unspecified' --> 'androidx.core:core:1.1.0-alpha04' --> 'androidx.concurrent:concurrent-futures:1.0.0-alpha02' --> 'com.google.guava:listenablefuture:1.0'
           Constraint path 'example:mymodule:unspecified' --> 'com.google.guava:listenablefuture:{strictly 1.0}' because of the following reason: debugRuntimeClasspath uses version 1.0
           ...

If I understand correctly:

  • Truth 0.43 depends on Guava 27
  • Guava 27 depends on 9999.0-empty because it internally contains a copy of ListenableFuture and version 9999.0-empty is well "empty".
  • other libs like AndroidX core depend on listenablefuture:1.0
  • When resolving conflicts (should I use version 1.0 or 9999.0?) Gradle will pick the latest version, in this case 9999.0.
  • So AndroidX core is actually getting ListenableFuture from classpath via Guava, but it's none-the-wiser.
  • However, the strictly keyword is forcing the usage of 1.0
  • this is happening because Android Gradle Plugin forces runtime and compiletime class paths to depend on same version =( read more here

So at the moment I'm stuck as to what to do now.

like image 353
tir38 Avatar asked Feb 11 '19 17:02

tir38


2 Answers

To fix the conflict, merely adding a dependency on Guava 27 should be enough. Doing so should automatically (though the "Version 99 Does Not Exist" hack described in the post you linked) prevent a second copy of ListenableFuture from being pulled in. (Please let us know if not!)

Since you're working on an Android library, it sounds like you want guava-27.0.1-android rather than -jre.

I don't know anything about your side question, sorry. And sorry for the lack of a response for so long.

like image 138
Chris Povirk Avatar answered Oct 13 '22 14:10

Chris Povirk


something alike this should prevent the duplicate:

implementation ("com.google.android.material:material:1.1.0-alpha03") {
    exclude group: "com.google.guava", module: "listenablefuture"
}

the part which updates the library seems correct:

androidTestImplementation "com.google.guava:guava:27.0.1-android"
androidTestImplementation ("com.google.truth:truth:0.42") {
    exclude group: "com.google.guava", module: "guava"
}

can only assume without a build.gradle.

like image 42
Martin Zeitler Avatar answered Oct 13 '22 15:10

Martin Zeitler