On 20 Apr 2014, at 7:38 am, Szczepan Faber <szczepan.fa...@gradleware.com> 
wrote:

> I was asked by the client to perform a quick spike on conflict resolution 
> rules. I decided spike it first before kicking off a discussion. Here're my 
> findings.
> 
> Intro.
> 
> Currently, a version conflict means that there are modules with the same 
> group+name but different version. However, there is also a different class of 
> conflicts - different modules (different group or name) are conflicting and 
> only one of them should be chosen. In the spike, the ResolutionStrategy a) 
> allows declaring that certain modules are conflicting and b) allows to choose 
> preferred module version in case of such conflict.
> 
> Some specific use cases I found so far:
> 
>  1. 'com.google.collections:google-collections' and 'com.google.guava:guava' 
> are conflicting, prefer guava.
>  2. 'org.jboss.netty:netty' and 'io.netty:netty*' are conflicting, prefer 
> io.netty.
>  3. 'org.springframework:spring' and 'org.springframework:spring-*' are 
> conflicting, prefer 'org.springframework:spring-*' (for example, spring-core).
>  4. 'kafka:kafka_2.8.2' VS 'kafka:kafka_2.10' VS 'kafka:kafka' are 
> conflicting, prefer _2.10 (say)
>  5. A team in an organization decided to extract a standalone project out of 
> a bigger project. The group id of the modules moved to a new project needed 
> to change. Now there's a risk that consumers will have problems with conflict 
> resolution.

You can boil this down to 3 use cases:

1. A given component moves to some new coordinates. This would be #1 and #2 
above.
2. A given component is repackaged, by splitting it into a set of smaller 
components. This would be #3 and #5 above. Groovy 1.x -> 2.x is another example 
of this.
3. A given component is build for multiple versions of some runtime. This would 
be #4 above, where Scala is the runtime.

You can generalise #1 and #2 into a ‘replaces’ abstraction:

- com.google.guava:guava replaces com.google.collections.google-collections
- io.netty:netty-all replaces org.jboss.netty:netty
- io.netty:(everything except -all and -parent) replace org.jboss.netty:netty
- my.org:(a, b) replace my.old.org:ab

This can also be used to model Maven relocations too.

Given we know that a replaces b, we can do two things:

1. Detect conflicts. Only one of a or b can be present in the result.
2. Resolve the conflict. The result should include a and not b.

We should model #3 using a ‘runtime’ abstraction.

Given that we know that ‘a_2.10’ is ‘a’ with runtime Scala 2.10 and ‘a_2.8’ is 
‘a’ with runtime Scala 2.8, we can:

1. Detect conflicts. Only one of ‘a’, ‘a_2.10’ and ‘a_2.8’ can be present in 
the result.
2. Resolve the conflict by selecting the components with the “best" Scala 
runtime.
3. Substitue ‘a_2.8’ with ‘a_2.10’ or ‘a’ when the “best” Scala runtime is not 
Scala 2.8.

By “best” Scala runtime this would be either the Scala version we’re building 
for, which we can infer from our direct dependencies, or the latest Scala 
runtime that appears in the graph if we’re not building for Scala.

For all these use cases, these facts are something about the component that is 
independent about which graphs the component happens to appear in. So, they 
should be declared for the component meta-data, using, say, component meta-data 
rules. I wouldn’t use resolution strategy for this.

This way we can reuse these facts in other contexts, such as detecting and 
dealing with conflicts in mapping to the IDE dependency models.


--
Adam Murdoch
Gradle Co-founder
http://www.gradle.org
VP of Engineering, Gradleware Inc. - Gradle Training, Support, Consulting
http://www.gradleware.com

Join us for Gradle Summit 2014, June 12th and 13th in Santa Clara, CA: 
http://www.gradlesummit.com

Reply via email to