Managing transitive dependencies in gradle
Recently I can across a problem with android dependencies when upgrading some of our components. The problem was that the library org.apache.commons:commons-lang3
broke compatibility with Android version 6 and earlier. The issue was introduced in the commons-lang3 library at version 3.10, if we used version 3.9 or earlier we were fine anything later and it would fail.
I did some experimentation and found that I was able to control my project dependencies using gradle. At its simplist I could change the reference from this
implementation "org.apache.commons:commons-lang3:3.9"
to this
implementation ("org.apache.commons:commons-lang3") {
version {
strictly "3.9"
}
because "new versions of apache commons dont work with android v6 - sdk23 - see https://issues.apache.org/jira/browse/LANG-1532"
}
Now whenever any of my dependent libraries tries to use a newer version of org.apache.commons:commons-lang3
then an error would be thrown. I think its a good idea to specify a good because
clause as in a team of developers you need to be clear for the reason to restrict the specified version.
Cannot find a version of 'org.apache.commons:commons-lang3' that satisfies the version constraints:
Dependency path 'app:unspecified' --> 'com.example:dependent-library:1.2.3' --> 'org.apache.commons:commons-lang3:3.10'
Dependency path 'app:unspecified' --> 'com.example:app:unspecified' --> 'org.apache.commons:commons-lang3:{strictly 3.9}' because of the following reason: new versions of apache commons dont work with android v6 - sdk23 - see https://issues.apache.org/jira/browse/LANG-1532
In this example my app is using a dependent-library
and version 1.2.3 of that library has a reference to org.apache.commons:commons-lang3:3.10
This is obviously a problem, but at least it is visible, Gradle’s default behaviour is to silently upgrade everyone to 3.10 which is not ideal. In my case none of my dependencies tripped this error. If they had then there are a number of options for solving the problem. If I had access to the source of dependent-library
then maybe I could change its dependency, or alternatively I could use an earlier version of dependent-library
, or remove the library completely.
If none of these can be used then another, though potentially dangerous solution is to override Gradle’s default behaviour by doing this in the top level build.gradle
file
allprojects {
configurations.all {
resolutionStrategy {
dependencySubstitution {
substitute(module("org.apache.commons:commons-lang3")).with(module("org.apache.commons:commons-lang3:3.9"))
}
}
}
Dont forget this is potentially very dangerous as dependent-library
will now be forced to use version 3.9 even though it wanted 3.10 and it has probably never been tested in this configuration. Also this substitution will fail if the library has also specified a strictly
reference