The forced versions are applied to the dependency graph (if groovy or groovy-all is seen, the target version is set to 2.4.5).
The dependency substitution rule is applied to the dependency graph (if groovy-all is seen, the target version is set to 2.4.1). The dependency substitution rule sets the target version to the requested version, which is 2.4.1.
When you remove the dependency substitution rule, it works because you’re no longer setting the target to the requested version.
When you add an explicit dependency on groovy, it works because the dependency substitution rule doesn’t change it and you’re forcing the version to be 2.4.5. Dependency conflict resolution kicks in at this point (we have both 2.4.1 and 2.4.5 in the graph, so we choose the latest).
You can see this with gradle dependencyInsight --dependency groovy for both cases or printing out requested and target in the dependency substitution rule.
Reapplying the forced version rules might not make sense, since the eachDependency dependency resolve rules are supposed to allow you to do very specific things just before we resolve dependencies.
In practice the “don’t use groovy-all” rule comes from a plugin enforcing enterprise-wide conventions, while the “always use 2.4.5” is coming from the particular application build.
I am trying not to introduce too many magical properties, that one would need to know. In this case we are using the core Gradle facilities and the intent is quite unambiguous. Given that I am passing-through the requested.version, I would expect the forced version to be applied at some point before or after the resolution.
If I am to describe the current behavior from external view, it would come out as:
When using force(), we guarantee that the artifacts in question will always be resolved to the specified version, UNLESS they are transient dependencies AND are matching an existing dependency substitution rule (i.e. specified in resolutionStrategy.eachDependency { useTarget(...) })
Not quite what I’d call intuitive or easy to remember…
I assume you mean “transitive dependencies” (not transient)? I don’t think that has any direct bearing here.
The rule is,
When using force(), we guarantee that the modules will always be resolved to the specified version, unless a dependency resolve rule provides a different target version.
In your example, the dependency resolve rule overrides the target version (from 2.4.5 back to 2.4.1). The requested version is the version from the POM/Ivy metadata or Dependency.
Given your build script (with a direct dependency on groovy), we have these dependencies before applying any force/resolve rules:
Thanks Sterling, I understand what is going on now and what I need is relatively straightforward to achieve using the existing API. In case somebody has the same problem, I changed my plugin to: