Clone
Cédric Champeau
committed
on 11 Jan
Only allow more than one variant of a component if it has different capabilities
This commit significantly reworks how capabilities are hand… Show more
Only allow more than one variant of a component if it has different capabilities

This commit significantly reworks how capabilities are handled, in particular

with regards to accepting 2 different variants of the same component to appear

in a graph. It is now allowed to have 2 variants of the same component if they

have distinct capabilities.

It shall be reminded that if a variant doesn't explicitly declare any capability,

it is assumed to carry the _implicit capability_, which corresponds to the GAV

of the component. Said differently, by default, all variants of a component

are supposed to be incompatible with each other (they cannot appear in the

same dependency graph).

If 2 variants are indeed compatible, then they _must_ have different capabilities.

This will be used to support optional dependencies, by making it possible to

express that one has to choose a specific set of compatible variants to enable

some feature.

There are however a few issues that needed to be addressed in this commit. First

of all, what we've just described is only true if we use variant-aware selection,

and more specifically attribute-based variant selection. If not, then a legacy

mode is used, and it's possible to have 2 "variants" of a component on the same

graph. This is typically the case when there are dependencies on 2 different

_configurations_ of a project:

   dependencies {

      api project(path: ':foo', configuration: 'conf1')

      api project(path: ':foo', configuration: 'conf2')

   }

For backwards compatibility reason, even if the 2 configurations actually

provide the same capability, this is allowed. Another case is when a cycle is

found between a root component and a transitive dependency corresponding to

the same component in a lower version.

Last but not least, in the engine the implicit capability is treated

specially for performance reasons: capability conflict resolution is very

expensive, and adding an explicit capability to all components would trigger

conflict resolution everywhere (to find out if other components provide the

same capability). This leads to some optimizations in the code which are not

necessarily easy to follow, but properly documented.

Show less