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