Now, what I would like to happen is that Gradle notices that junit wants hamcrest-1.1, sees that my version range is compatible with that, and selects hamcrest-1.1. What actually happens is this:
Could not resolve all dependencies for configuration ':mymodule:testCompile'.
> A conflict was found between the following modules:
That is, Gradle says: “Oh, let me pick the latest version from your range, and then immediately forget that you specified a range. That means we pick hamcrest-1.3” Then it notices that junit wants hamcrest-1.1, and then it blows up with a version conflict. IMO, this is worse than useless. At least it explains why the docs discourage using ranges: because they pretty much don’t do the logical and useful thing.
Just in case the logical and useful thing is not clear, let me spell it out: the resolution strategy should be to find the intersection of all version ranges, and choose the highest version in that set.
The error is the result of resolutionStrategy.failOnVersionConflict() which fails due to two requested versions of hamcrest (1.3 which was chosen by the range selector, and 1.1 which is a dependency of junit 4.11). If you simply remove the call to failOnVersionConflict() Gradle will correctly resolve the conflict to the latest version, in this case, 1.3.
I think I now understand the confusion here. The range selector does not resolve to the latest requested version but the latest available version dependening on the repositories you have configured. In the end the resolved graph contains two requested versions, which introduces a conflict that must be resolved.
From what I can tell, it is not possible to implement the ‘latest-compatible’ semantics in Gradle. This is because:
A) The only option during componentSelection is to reject some version presented by the resolver
B) You must make this decision before seeing other instances of this module dependency
Therefore, the set intersection operation is impossible just using the componentSelection and eachDependency mechanisms. It would require something like a 2-pass resolution where all of the nodes in the dependency DAG are visited in the first pass, and the actual versions are selected in the second pass.
So, unfortunately, the Gradle resolver is strictly less powerful than the Ivy conflict managers. This is ironic and frustrating, given that we almost have the full power of Groovy available, except when we don’t.
While I’m not familiar with the ‘latest-compatible’ conflict resolver in Ivy, I agree that it’s probably not possible (or at least very difficult) to replicate this behaviour in Gradle.
We are planning to improve the way we select the graph of ‘compatible’ versions for a set of dependencies: this is likely to be driven by the introduction of ‘variant-aware’ resolution, which we are currently working on. This work will require some deep refactoring of our dependency engine.