More thoughts on conflict resolution, Adam-style :)

1. The starting use case is avoiding conflicting guava and
google-collections on the same classpath. We'll enable that by
offering an API to declare that a module (group+name)
replaces/supersedes/deprecates a different module (group+name). This
use is a starting point to more interesting use cases which I will
describe in later. While designing the DSL and the behavior we need to
be cautious about those other use cases, so that we can develop this
feature incrementally. We will use guava an collections as an example.

2. In a standalone project, a guava-collections issue is not that
problematic, it can be sorted out by editing the build file (change
the dependencies, add an 'exclude', add force rule, etc.). The use
case is getting interesting in on an enterprise level, where the
organisation wants to solve the problem in the shared plugin suite,
instead of getting each team solve the problem on their own.
Well-known deprecations like collections-guava could be even encoded
in Gradle out of the box, but that's a different topic.

3. Current APIs for influencing the dependency resolution (forcing
versions, dependency resolve rules) offer only "unconditional"
manipulation to the dependencies that are being resolved. Let's map
the collections-guava problem to the current API: we could force
collections to become guava via dependency resolve rule. The effect
is:
 - only collections in graph - guava is used
 a) only guava in graph - guava is used
 b) both collections in graph - guava is used (collections are forced
into guava)
 c) only collections in graph - guava is used
This is what I mean by 'unconditional' manipulation - if collections
appear in graph it is _always_ replaced with guava. And this is not
quite what we want...

4. We want the replacement rule to get activated only when both
collections and guava are in the graph. If only collections appear in
the graph - there is no need to replace it with guava. Perhaps guava
and collection is not the best example because early guava is
completely backwards compatible with latest collections. So in theory,
we could unconditionally replace collections with an early version of
guava via existing dependency resolve rule API. However, let's imagine
that replacement source and target are not that quite compatible or
that someone is using an _early_ version of collections which is not
quite compatible with _early_ guava.

5. The implementation is tricker than our current forcing/dependency
resolve rules. Consider dependency graph traversal scenarios:
 - first we encounter collections, we happily use it and continue traversing.
 - then we encounter guava - oups - we need to evict already resolved
module collections and continue traversing. (It's more complicated
than that, as we resolve conflicts only when we cannot traverse any
further).

In fact, it does not really differ from the standard, version-based
conflict resolution. I wanted to point out the difference between
existing dependency resolve rules implementation. The latter does not
need to consider already-resolved modules.

6. Other features to consider:
 - module is replaced with a set of modules ('spring' ->
'spring-core', 'spring-aop', etc.)
 - set of modules is replaced with a single module (I don't know any
real example for that)
 - api module and impl module use consistent version

7. Given that there are more ways to influence the dependency
resolution, and specifically, to replace/force certain dependencies
with others we should think about improving diagnostics on why certain
dependency ended up in the graph and why this particular version is
used. Our reports and API already provides 'selection reason'
information but I'm afraid it's not enough. A dependency can be
manipulated during resolution by different rules, applied by different
plugins and a flat information like 'dependency resolve rule was used'
is not enough IMHO. There can be a stream of actions that affects the
selected version of a dependency.

8. I made up my mind about the DSL. I'm easy and I can totally be
convinced to something else. Yet, here's my current preference:

 a) I would reuse some existing low-level, imperative API, like the
component rules or dependency resolve rules, for example:

  eachDependency { details ->
    if (details.target.name == 'google-collections') {
     details.replacedBy 'com.google.guava:guava'
    }
  }

 b) I would add some higher level api that would use a). For example:

 dependencies {
   components {
     
modules("com.google.collections:google-collections").deprecatedBy("com.google.guava:guava")
     //or replacedBy(), supersededBy()
   }
 }

Thoughts?

On Fri, Aug 1, 2014 at 11:30 AM, Szczepan Faber <szcze...@gmail.com> wrote:
> This is a typo, the spec is work in progress. Initially we thought
> about 'replaces' but we've changed that into 'replacedBy' but I forgot
> to reverse the source and target :)
>
> So:
>
> google-collections is replaced by guava
> guava replaces google-collections
>
> Thanks for pointing this out. I'll do more speccing and get back to
> the mailing list.
>
> Cheers!
>
> On Fri, Aug 1, 2014 at 9:38 AM, Heinrich Filter
> <heinrich.fil...@mmiholdings.co.za> wrote:
>> Hi Szczepan,
>>
>> I'm new around the Gradle community but would love to start contributing 
>> more. Please feel free to point out any "conventions" that I miss.
>>
>> For me the "replacedBy" keyword is a bit confusing. In your example: 'guava' 
>> was replace by 'google-collections'. In my mind that implies that 
>> 'google-collection' is the *newer* dependency and not, as you state in the 
>> doc, the older version.
>>
>> I would think that 'guava' rather *replaces* or *supersedes* 
>> 'google-collections'.
>>
>> Or am thinking about this the wrong way?
>>
>> Regards,
>> Heinrich
>>
>> On 31 Jul 2014, at 3:27 PM, Szczepan Faber <szcze...@gmail.com> wrote:
>>
>>> Hey,
>>>
>>> I've started speccing improvements to conflict resolution here:
>>> https://github.com/gradle/gradle/blob/master/design-docs/conflict-resolution-spec.md
>>> (there's a big DSL mock-up down the bottom).
>>>
>>> It would be good to start discussing the DSL additions here on the
>>> mailing list. Please give feedback about the DSL or the use cases,
>>> etc. I haven't decided myself on the DSL :) I'll keep you in loop on
>>> how the design goes.
>>>
>>> Cheers!
>>> --
>>> Szczepan Faber
>>> Core dev@gradle; Founder@mockito
>>>
>>> ---------------------------------------------------------------------
>>> To unsubscribe from this list, please visit:
>>>
>>>    http://xircles.codehaus.org/manage_email
>>>
>>>
>>
>>
>> **********************************************************************
>> This email and all content are subject to the following disclaimer
>>
>> http://content.momentum.co.za/content/legal/disclaimer_email.htm
>>
>> **********************************************************************
>>
>> ---------------------------------------------------------------------
>> To unsubscribe from this list, please visit:
>>
>>     http://xircles.codehaus.org/manage_email
>>
>>
>
>
>
> --
> Szczepan Faber
> Core dev@gradle; Founder@mockito



-- 
Szczepan Faber
Core dev@gradle; Founder@mockito

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply via email to